Skip to content

Commit 526b9eb

Browse files
committed
Use faucet-pipeline-static/lib/util
1 parent f366807 commit 526b9eb

File tree

4 files changed

+135
-78
lines changed

4 files changed

+135
-78
lines changed
Lines changed: 86 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
let path = require("path");
2-
let { FileFinder } = require("faucet-pipeline-core/lib/util/files/finder");
1+
let path = require("node:path");
32
let sharp = require("sharp");
43
let 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> */
6061
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")
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+
*/
136104
async 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+
*/
147122
async 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+
*/
185172
function 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+
*/
193184
function 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+
*/
198194
function 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+
**/

lib/types.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export interface Config {
2+
source: string,
3+
target: string,
4+
targetDir: string,
5+
fingerprint?: boolean,
6+
assetManager: AssetManager,
7+
filter?: Filter
8+
}
9+
10+
export interface ImageOptions {
11+
autorotate: boolean,
12+
width: number,
13+
height: number,
14+
scale: number,
15+
quality: number,
16+
crop: boolean
17+
}
18+
19+
export interface TargetPathOpts {
20+
format: string,
21+
suffix: string
22+
}
23+
24+
/** @import {
25+
* AssetManager,
26+
* Filter
27+
* } from "faucet-pipeline-static/lib/types.ts"
28+
**/

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
"name": "faucet-pipeline-images",
33
"version": "2.2.0",
44
"description": "image optimization for faucet-pipeline",
5-
"main": "index.js",
5+
"main": "lib/index.js",
66
"scripts": {
77
"test": "npm run lint && npm run test:cli",
88
"test:cli": "./test/run",
99
"test:prepare": "./test/prepare",
10-
"lint": "eslint --cache index.js test && echo ✓"
10+
"lint": "eslint --cache lib test && echo ✓",
11+
"typecheck": "tsc"
1112
},
1213
"repository": {
1314
"type": "git",
@@ -24,11 +25,14 @@
2425
},
2526
"dependencies": {
2627
"faucet-pipeline-core": "^3.0.0",
28+
"faucet-pipeline-static": "git+https://github.com/faucet-pipeline/faucet-pipeline-static.git#dual-versions",
2729
"sharp": "^0.34.0",
2830
"svgo": "^3.3.2"
2931
},
3032
"devDependencies": {
33+
"@types/node": "^22.13.10",
3134
"eslint-config-fnd": "^1.13.0",
32-
"release-util-fnd": "^3.0.0"
35+
"release-util-fnd": "^3.0.0",
36+
"typescript": "^5.8.2"
3337
}
3438
}

tsconfig.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"compilerOptions": {
3+
"allowJs": true,
4+
"checkJs": true,
5+
"noEmit": true,
6+
"strict": true,
7+
"allowImportingTsExtensions": true,
8+
"lib": ["dom", "es2023"],
9+
"module": "nodenext"
10+
},
11+
"exclude": [
12+
"./test/**",
13+
]
14+
}

0 commit comments

Comments
 (0)