-
Notifications
You must be signed in to change notification settings - Fork 0
/
optimize-img.js
132 lines (113 loc) Β· 3.75 KB
/
optimize-img.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
const sharp = require('sharp');
const path = require('path');
const { promises: fs } = require('fs');
const R = require('ramda');
const sourceDir = 'public/img';
const destDir = 'public/optimized-img';
const notEndsWith = (extension) =>
R.filter((node) => R.not(R.endsWith(extension, node)));
const filterUnwantedExtensions = R.compose(
notEndsWith('.svg'),
notEndsWith('.gif'),
notEndsWith('.DS_Store')
);
const walkDirs = (source) =>
R.map(async (node) => {
const nextPath = path.join(source, node);
const stat = await fs.stat(nextPath);
return stat.isDirectory() ? await lsFiles(nextPath) : nextPath;
});
/**
* Recursively get a list of all leaf file paths in a directory
*/
const lsFiles = async (source) => {
const contents = await fs.readdir(source);
const transducer = R.compose(filterUnwantedExtensions, walkDirs(source));
const files = R.transduce(transducer, R.flip(R.append), [], contents);
return R.flatten(await Promise.all(files));
};
const computePathAndName = R.map((f) => {
const components = f.split('/');
return {
filePath: R.join('/', R.dropLast(1, components)),
name: R.last(components),
};
});
const appendOptimizedPath = R.map((pathAndName) => ({
...pathAndName,
optimizedPath: path.join(
R.replace(sourceDir, destDir, pathAndName.filePath),
pathAndName.name
),
}));
const tentativeOptimizedPaths = (imgFiles) => {
const transducer = R.compose(computePathAndName, appendOptimizedPath);
return R.transduce(transducer, R.flip(R.append), [], imgFiles);
};
/**
* Remove the processed file names like w-20.jpeg
* and return just the path
*/
const stripFileNames = (processedImgFiles) => {
const txf = R.compose(R.join('/'), R.dropLast(1), R.split('/'));
return R.uniq(R.map(txf, processedImgFiles));
};
const filesToProcess = (imgFiles, processedImgFiles) => {
const computedOutComponents = tentativeOptimizedPaths(imgFiles);
const existingOptimizedPaths = stripFileNames(processedImgFiles);
return R.filter(
(c) => R.not(R.includes(c.optimizedPath, existingOptimizedPaths)),
computedOutComponents
);
};
const widthsToGenerate = [80, 240, 480, 720, 960, 1440];
const widthsToBlur = [480, 960];
const processFile = async (component) => {
// create the folder
await fs.mkdir(component.optimizedPath, { recursive: true });
// convert image to webp format in highest quality
const inputSharp = sharp(path.join(component.filePath, component.name));
const ogWebpPath = path.join(component.optimizedPath, 'og.webp');
await inputSharp.toFile(ogWebpPath);
// convert og.webp to desired widths
const ogSharp = sharp(ogWebpPath);
await Promise.all(
R.map(async (width) => {
const widthPath = path.join(component.optimizedPath, `w-${width}.webp`);
await ogSharp.clone().resize({ width }).toFile(widthPath);
// blur if needed
if (R.includes(width, widthsToBlur)) {
await sharp(widthPath)
.blur(8)
.toFile(
path.join(component.optimizedPath, `w-${width}-blurred.webp`)
);
}
}, widthsToGenerate)
);
};
async function main() {
const imgFiles = await lsFiles(sourceDir);
console.log(`π§ Found ${imgFiles.length} source images`);
const processedImgFiles = await lsFiles(destDir);
const unprocessedImgFiles = filesToProcess(imgFiles, processedImgFiles);
console.log(`πͺ£ ${unprocessedImgFiles.length} images need to be processed`);
try {
await Promise.all(R.map(await processFile, unprocessedImgFiles));
console.log('β
Images optimized successfully!');
} catch (e) {
console.error(e);
}
}
module.exports = {
filterUnwantedExtensions,
computePathAndName,
sourceDir,
destDir,
appendOptimizedPath,
stripFileNames,
filesToProcess,
};
if (require.main === module) {
main();
}