1- let  path  =  require ( "path" ) ; 
2- let  {  FileFinder }  =  require ( "faucet-pipeline-core/lib/util/files/finder" ) ; 
3- let  sharp  =  require ( "sharp" ) ; 
4- let  svgo  =  require ( "svgo" ) ; 
5- let  {  stat,  readFile }  =  require ( "fs" ) . promises ; 
6- let  {  abort }  =  require ( "faucet-pipeline-core/lib/util" ) ; 
1+ import  path  from  "node:path" ; 
2+ import  sharp  from  "sharp" ; 
3+ import  svgo  from  "svgo" ; 
4+ import  {  readFile  }  from  "node:fs/promises" ; 
5+ import  {  buildProcessPipeline  }  from  "faucet-pipeline-assets/lib/util.js" ; 
6+ import  {  abort ,  repr  }  from  "faucet-pipeline-core/lib/util/index.js" ; 
7+ 
8+ export  const  key  =  "images" ; 
9+ export  const  bucket  =  "static" ; 
10+ 
11+ export  function  plugin ( config ,  assetManager )  { 
12+ 	let  pipeline  =  config . map ( optimizerConfig  =>  { 
13+ 		let  processFile  =  buildProcessFile ( optimizerConfig ) ; 
14+ 		let  {  source,  target }  =  optimizerConfig ; 
15+ 		let  filter  =  optimizerConfig . filter  || 
16+ 			withFileExtension ( "avif" ,  "jpg" ,  "jpeg" ,  "png" ,  "webp" ,  "svg" ) ; 
17+ 		return  buildProcessPipeline ( source ,  target ,  processFile ,  assetManager ,  filter ) ; 
18+ 	} ) ; 
19+ 
20+ 	return  filepaths  =>  Promise . all ( pipeline . map ( optimize  =>  optimize ( filepaths ) ) ) ; 
21+ } 
722
823// we can optimize the settings here, but some would require libvips 
924// to be compiled with additional stuff 
@@ -51,99 +66,54 @@ let settings = {
5166	avif : { } 
5267} ; 
5368
54- module . exports  =  { 
55- 	key : "images" , 
56- 	bucket : "static" , 
57- 	plugin : faucetImages 
58- } ; 
59- 
60- function  faucetImages ( config ,  assetManager )  { 
61- 	let  optimizers  =  config . map ( optimizerConfig  => 
62- 		makeOptimizer ( optimizerConfig ,  assetManager ) ) ; 
63- 
64- 	return  filepaths  =>  Promise . all ( optimizers . map ( optimize  =>  optimize ( filepaths ) ) ) ; 
65- } 
66- 
67- function  makeOptimizer ( optimizerConfig ,  assetManager )  { 
68- 	let  source  =  assetManager . resolvePath ( optimizerConfig . source ) ; 
69- 	let  target  =  assetManager . resolvePath ( optimizerConfig . target ,  { 
70- 		enforceRelative : true 
71- 	} ) ; 
72- 	let  fileFinder  =  new  FileFinder ( source ,  { 
73- 		skipDotfiles : true , 
74- 		filter : optimizerConfig . filter  || 
75- 			withFileExtension ( "avif" ,  "jpg" ,  "jpeg" ,  "png" ,  "webp" ,  "svg" ) 
76- 	} ) ; 
77- 	let  { 
78- 		autorotate, 
79- 		fingerprint, 
80- 		format, 
81- 		width, 
82- 		height, 
83- 		crop, 
84- 		quality, 
85- 		scale, 
86- 		suffix
87- 	}  =  optimizerConfig ; 
88- 
89- 	return  async  filepaths  =>  { 
90- 		let  [ fileNames ,  targetDir ]  =  await  Promise . all ( [ 
91- 			( filepaths  ? fileFinder . match ( filepaths )  : fileFinder . all ( ) ) , 
92- 			determineTargetDir ( source ,  target ) 
93- 		] ) ; 
94- 		return  processFiles ( fileNames ,  { 
95- 			assetManager, 
96- 			source, 
97- 			target, 
98- 			targetDir, 
99- 			fingerprint, 
100- 			variant : { 
101- 				autorotate,  format,  width,  height,  crop,  quality,  scale,  suffix
102- 			} 
103- 		} ) ; 
69+ /** 
70+  * Returns a function that processes a single file 
71+  */ 
72+ function  buildProcessFile ( config )  { 
73+ 	return  async  function ( filename , 
74+ 			{  source,  target,  targetDir,  assetManager } )  { 
75+ 		let  sourcePath  =  path . join ( source ,  filename ) ; 
76+ 		let  targetPath  =  determineTargetPath ( path . join ( target ,  filename ) ,  config ) ; 
77+ 
78+ 		let  format  =  config . format  ? config . format  : extname ( filename ) ; 
79+ 
80+ 		let  output  =  format  ===  "svg"  ?
81+ 			await  optimizeSVG ( sourcePath )  :
82+ 			await  optimizeBitmap ( sourcePath ,  format ,  config ) ; 
83+ 
84+ 		let  writeOptions  =  {  targetDir } ; 
85+ 		if ( config . fingerprint  !==  undefined )  { 
86+ 			writeOptions . fingerprint  =  config . fingerprint ; 
87+ 		} 
88+ 		return  assetManager . writeFile ( targetPath ,  output ,  writeOptions ) ; 
10489	} ; 
10590} 
10691
107- // If `source` is a directory, `target` is used as target directory - 
108- // otherwise, `target`'s parent directory is used 
109- async  function  determineTargetDir ( source ,  target )  { 
110- 	let  results  =  await  stat ( source ) ; 
111- 	return  results . isDirectory ( )  ? target  : path . dirname ( target ) ; 
112- } 
113- 
114- async  function  processFiles ( fileNames ,  config )  { 
115- 	return  Promise . all ( fileNames . map ( fileName  =>  processFile ( fileName ,  config ) ) ) ; 
116- } 
117- 
118- async  function  processFile ( fileName , 
119- 		{  source,  target,  targetDir,  fingerprint,  assetManager,  variant } )  { 
120- 	let  sourcePath  =  path . join ( source ,  fileName ) ; 
121- 	let  targetPath  =  determineTargetPath ( path . join ( target ,  fileName ) ,  variant ) ; 
122- 
123- 	let  format  =  variant . format  ? variant . format  : extname ( fileName ) ; 
124- 
125- 	let  output  =  format  ===  "svg"  ?
126- 		await  optimizeSVG ( sourcePath )  :
127- 		await  optimizeBitmap ( sourcePath ,  format ,  variant ) ; 
128- 
129- 	let  writeOptions  =  {  targetDir } ; 
130- 	if ( fingerprint  !==  undefined )  { 
131- 		writeOptions . fingerprint  =  fingerprint ; 
132- 	} 
133- 	return  assetManager . writeFile ( targetPath ,  output ,  writeOptions ) ; 
134- } 
135- 
92+ /** 
93+  * Optimize a single SVG 
94+  * 
95+  * @param  {string } sourcePath 
96+  * @returns  {Promise<string> } 
97+  */ 
13698async  function  optimizeSVG ( sourcePath )  { 
13799	let  input  =  await  readFile ( sourcePath ) ; 
138100
139101	try  { 
140102		let  output  =  await  svgo . optimize ( input ,  settings . svg ) ; 
141103		return  output . data ; 
142104	}  catch ( error )  { 
143- 		abort ( `Only SVG can be converted to SVG: ${ sourcePath }  ` ) ; 
105+ 		abort ( `Only SVG can be converted to SVG: ${ repr ( sourcePath ) }  ` ) ; 
144106	} 
145107} 
146108
109+ /** 
110+  * Optimize a single bitmap image 
111+  * 
112+  * @param  {string } sourcePath 
113+  * @param  {string } format 
114+  * @param  {Object } options 
115+  * @returns  {Promise<Buffer> } 
116+  */ 
147117async  function  optimizeBitmap ( sourcePath ,  format , 
148118		{  autorotate,  width,  height,  scale,  quality,  crop } )  { 
149119	let  image  =  sharp ( sourcePath ) ; 
@@ -153,7 +123,12 @@ async function optimizeBitmap(sourcePath, format,
153123
154124	if ( scale )  { 
155125		let  metadata  =  await  image . metadata ( ) ; 
156- 		image . resize ( {  width : metadata . width  *  scale ,  height : metadata . height  *  scale  } ) ; 
126+ 		if ( metadata . width  &&  metadata . height )  { 
127+ 			image . resize ( { 
128+ 				width : metadata . width  *  scale , 
129+ 				height : metadata . height  *  scale 
130+ 			} ) ; 
131+ 		} 
157132	} 
158133
159134	if ( width  ||  height )  { 
@@ -176,12 +151,17 @@ async function optimizeBitmap(sourcePath, format,
176151		image . avif ( {  ...settings . avif ,  quality } ) ; 
177152		break ; 
178153	default :
179- 		abort ( `unsupported format ${ format }  . We support: AVIF, JPG, PNG, WebP, SVG` ) ; 
154+ 		abort ( `unsupported format ${ repr ( format ) }  . We support: AVIF, JPG, PNG, WebP, SVG` ) ; 
180155	} 
181156
182157	return  image . toBuffer ( ) ; 
183158} 
184159
160+ /** 
161+  * @param  {string } filepath 
162+  * @param  {Object } options 
163+  * @returns  {string } 
164+  */ 
185165function  determineTargetPath ( filepath ,  {  format,  suffix =  ""  } )  { 
186166	format  =  format  ? `.${ format }  `  : "" ; 
187167	let  directory  =  path . dirname ( filepath ) ; 
@@ -190,11 +170,20 @@ function determineTargetPath(filepath, { format, suffix = "" }) {
190170	return  path . join ( directory ,  `${ basename } ${ suffix } ${ extension } ${ format }  ` ) ; 
191171} 
192172
173+ /** 
174+  * @param  {...string } extensions 
175+  * @returns  {Filter } 
176+  */ 
193177function  withFileExtension ( ...extensions )  { 
194178	return  filename  =>  extensions . includes ( extname ( filename ) ) ; 
195179} 
196180
197- // extname follows this annoying idea that the dot belongs to the extension 
181+ /** 
182+  * File extension of a filename without the dot 
183+  * 
184+  * @param  {string } filename 
185+  * @returns  {string } 
186+  */ 
198187function  extname ( filename )  { 
199188	return  path . extname ( filename ) . slice ( 1 ) . toLowerCase ( ) ; 
200189} 
0 commit comments