Skip to content

Commit a71c865

Browse files
committed
Use faucet-pipeline-static/lib/util
1 parent 4de9a7a commit a71c865

File tree

11 files changed

+184
-198
lines changed

11 files changed

+184
-198
lines changed

.github/workflows/tests.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
name: tests
21
on:
32
- push
3+
44
jobs:
5-
build:
5+
test:
66
runs-on: ubuntu-latest
77
strategy:
88
matrix:
99
node-version:
10-
- 18.x
10+
- 20.x
1111
- 22.x
12-
- latest
12+
- 24.x
1313
steps:
14-
- uses: actions/checkout@v4
15-
- uses: actions/setup-node@v4
14+
- uses: actions/checkout@v5
15+
- uses: actions/setup-node@v6
1616
with:
1717
node-version: ${{ matrix.node-version }}
1818
- run: npm run test:prepare && npm install-test

CHANGELOG.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
11
faucet-pipeline-images version history
22
======================================
33

4+
v2.3.0
5+
------
6+
7+
_TBD_
8+
9+
maintenance release to update dependencies
10+
11+
* bumped Node requirement to v20 or later, dropping support for obsolete versions
12+
413
v2.2.0
514
------
615

716
_2024-03-20_
817

9-
Maintenance release to update dependencies; bump minimum supported Node version
18+
maintenance release to update dependencies; bump minimum supported Node version
1019
to 18 due to end of life.
1120

1221
v2.1.0
1322
------
1423

1524
_2021-01-12_
1625

17-
Maintenance release to update dependencies; no significant changes
26+
maintenance release to update dependencies; no significant changes
1827

1928

2029
v2.0.0

index.js

Lines changed: 78 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
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-static/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+
*/
13698
async 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+
*/
147117
async 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+
*/
185165
function 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+
*/
193177
function 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+
*/
198187
function extname(filename) {
199188
return path.extname(filename).slice(1).toLowerCase();
200189
}

package.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
{
22
"name": "faucet-pipeline-images",
3-
"version": "2.2.0",
3+
"version": "2.3.0",
44
"description": "image optimization for faucet-pipeline",
5-
"main": "index.js",
5+
"main": "./index.js",
6+
"type": "module",
67
"scripts": {
78
"test": "npm run lint && npm run test:cli",
89
"test:cli": "./test/run",
910
"test:prepare": "./test/prepare",
10-
"lint": "eslint --cache index.js test && echo ✓"
11+
"lint": "eslint --cache ./index.js ./test && echo ✓"
1112
},
1213
"repository": {
1314
"type": "git",
@@ -20,11 +21,12 @@
2021
},
2122
"homepage": "https://www.faucet-pipeline.org",
2223
"engines": {
23-
"node": ">= 18"
24+
"node": ">= 20.19.0"
2425
},
2526
"dependencies": {
26-
"faucet-pipeline-core": "^3.0.0",
27-
"sharp": "^0.34.0",
27+
"faucet-pipeline-core": "^2.0.0 || ^3.0.0",
28+
"faucet-pipeline-static": "git+https://github.com/faucet-pipeline/faucet-pipeline-static.git#dual-versions",
29+
"sharp": "^0.34.1",
2830
"svgo": "^3.3.2"
2931
},
3032
"devDependencies": {

test/test_avif/faucet.config.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
"use strict";
2-
let path = require("path");
1+
import { resolve } from "node:path";
32

4-
module.exports = {
5-
images: [{
6-
source: "./src",
7-
target: "./dist",
8-
format: "avif"
9-
}],
10-
plugins: [path.resolve(__dirname, "../..")]
11-
};
3+
export const images = [{
4+
source: "./src",
5+
target: "./dist",
6+
format: "avif"
7+
}];
8+
9+
export const plugins = [resolve(import.meta.dirname, "../..")];

test/test_basic/faucet.config.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
"use strict";
2-
let path = require("path");
1+
import { resolve } from "node:path";
32

4-
module.exports = {
5-
images: [{
6-
source: "./src",
7-
target: "./dist"
8-
}],
9-
plugins: [path.resolve(__dirname, "../..")]
10-
};
3+
export const images = [{
4+
source: "./src",
5+
target: "./dist"
6+
}];
7+
8+
export const plugins = [resolve(import.meta.dirname, "../..")];

test/test_filter/faucet.config.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
"use strict";
2-
let path = require("path");
1+
import { resolve } from "node:path";
32

4-
module.exports = {
5-
images: [{
6-
source: "./src",
7-
target: "./dist",
8-
filter: file => file.endsWith(".jpg")
9-
}],
10-
plugins: [path.resolve(__dirname, "../..")]
11-
};
3+
export const images = [{
4+
source: "./src",
5+
target: "./dist",
6+
filter: file => file.endsWith(".jpg")
7+
}];
8+
9+
export const plugins = [resolve(import.meta.dirname, "../..")];

0 commit comments

Comments
 (0)