1- let path = require ( "path" ) ;
2- let { FileFinder } = require ( "faucet-pipeline-core/lib/util/files/finder" ) ;
1+ let path = require ( "node:path" ) ;
32let sharp = require ( "sharp" ) ;
43let svgo = require ( "svgo" ) ;
5- let { stat, readFile } = require ( "fs" ) . promises ;
6- let { abort } = require ( "faucet-pipeline-core/lib/util" ) ;
4+ let { readFile } = require ( "node:fs/promises" ) ;
5+ let { buildProcessPipeline } = require ( "faucet-pipeline-static/lib/util.js" ) ;
6+ let { abort } = require ( "faucet-pipeline-core/lib/util/index.js" ) ;
77
88// we can optimize the settings here, but some would require libvips
99// to be compiled with additional stuff
@@ -57,82 +57,50 @@ module.exports = {
5757 plugin : faucetImages
5858} ;
5959
60+ /** @type FaucetPlugin<Config> */
6061function 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" )
62+ let pipeline = config . map ( optimizerConfig => {
63+ let processFile = buildProcessFile ( optimizerConfig ) ;
64+ let { source, target } = optimizerConfig ;
65+ let filter = optimizerConfig . filter ||
66+ withFileExtension ( "avif" , "jpg" , "jpeg" , "png" , "webp" , "svg" ) ;
67+ return buildProcessPipeline ( source , target , processFile , assetManager , filter ) ;
7668 } ) ;
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- } ) ;
104- } ;
105- }
10669
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 ) ;
70+ return filepaths => Promise . all ( pipeline . map ( optimize => optimize ( filepaths ) ) ) ;
11271}
11372
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 ) ;
73+ /**
74+ * Returns a function that processes a single file
75+ *
76+ * @param {Config } config
77+ * @returns {ProcessFile }
78+ */
79+ function buildProcessFile ( config ) {
80+ return async function ( filename ,
81+ { source, target, targetDir, assetManager } ) {
82+ let sourcePath = path . join ( source , filename ) ;
83+ let targetPath = determineTargetPath ( path . join ( target , filename ) , config ) ;
84+
85+ let format = config . format ? config . format : extname ( filename ) ;
86+
87+ let output = format === "svg" ?
88+ await optimizeSVG ( sourcePath ) :
89+ await optimizeBitmap ( sourcePath , format , config ) ;
90+
91+ /** @type WriteFileOpts */
92+ let writeOptions = { targetDir } ;
93+ if ( config . fingerprint !== undefined ) {
94+ writeOptions . fingerprint = config . fingerprint ;
95+ }
96+ return assetManager . writeFile ( targetPath , output , writeOptions ) ;
97+ } ;
13498}
13599
100+ /**
101+ * @param {string } sourcePath
102+ * @returns {Promise<string> }
103+ */
136104async function optimizeSVG ( sourcePath ) {
137105 let input = await readFile ( sourcePath ) ;
138106
@@ -141,9 +109,16 @@ async function optimizeSVG(sourcePath) {
141109 return output . data ;
142110 } catch ( error ) {
143111 abort ( `Only SVG can be converted to SVG: ${ sourcePath } ` ) ;
112+ return "" ; // XXX: this is for typescript :joy:
144113 }
145114}
146115
116+ /**
117+ * @param {string } sourcePath
118+ * @param {string } format
119+ * @param {ImageOptions } options
120+ * @returns {Promise<Buffer> }
121+ */
147122async function optimizeBitmap ( sourcePath , format ,
148123 { autorotate, width, height, scale, quality, crop } ) {
149124 let image = sharp ( sourcePath ) ;
@@ -153,11 +128,18 @@ async function optimizeBitmap(sourcePath, format,
153128
154129 if ( scale ) {
155130 let metadata = await image . metadata ( ) ;
156- image . resize ( { width : metadata . width * scale , height : metadata . height * scale } ) ;
131+ if ( metadata . width && metadata . height ) {
132+ image . resize ( {
133+ width : metadata . width * scale ,
134+ height : metadata . height * scale
135+ } ) ;
136+ }
157137 }
158138
159139 if ( width || height ) {
160140 let fit = crop ? "cover" : "inside" ;
141+ // XXX: Fixme
142+ // @ts -ignore
161143 image . resize ( { width, height, fit : sharp . fit [ fit ] } ) ;
162144 }
163145
@@ -182,6 +164,11 @@ async function optimizeBitmap(sourcePath, format,
182164 return image . toBuffer ( ) ;
183165}
184166
167+ /**
168+ * @param {string } filepath
169+ * @param {TargetPathOpts } opts
170+ * @returns {string }
171+ */
185172function determineTargetPath ( filepath , { format, suffix = "" } ) {
186173 format = format ? `.${ format } ` : "" ;
187174 let directory = path . dirname ( filepath ) ;
@@ -190,11 +177,35 @@ function determineTargetPath(filepath, { format, suffix = "" }) {
190177 return path . join ( directory , `${ basename } ${ suffix } ${ extension } ${ format } ` ) ;
191178}
192179
180+ /**
181+ * @param {...string } extensions
182+ * @returns {Filter }
183+ */
193184function withFileExtension ( ...extensions ) {
194185 return filename => extensions . includes ( extname ( filename ) ) ;
195186}
196187
197- // extname follows this annoying idea that the dot belongs to the extension
188+ /**
189+ * extname follows this annoying idea that the dot belongs to the extension
190+ *
191+ * @param {string } filename
192+ * @returns {string }
193+ */
198194function extname ( filename ) {
199195 return path . extname ( filename ) . slice ( 1 ) . toLowerCase ( ) ;
200196}
197+
198+ /** @import {
199+ * FaucetPlugin,
200+ * FaucetPluginOptions,
201+ * WriteFileOpts,
202+ * Filter,
203+ * ProcessFile
204+ * } from "faucet-pipeline-static/lib/types.ts"
205+ **/
206+ /** @import {
207+ * Config,
208+ * ImageOptions,
209+ * TargetPathOpts
210+ * } from "./types.ts"
211+ **/
0 commit comments