+113
-48
src/build-shit.ts
+113
-48
src/build-shit.ts
···
11
const fsp = fs.promises;
12
const STYLESDIR = 'styles';
13
const SCRIPTSDIR = 'scripts';
14
-
const IMAGESDIR = path.join('assets', 'images', 'original');
15
const STYLEOUTDIR = process.env.STYLEOUTDIR || path.join('assets', 'css');
16
const SCRIPTSOUTDIR = process.env.SCRIPTSOUTDIR || path.join('assets', 'js');
17
-
const IMAGESOUTDIR = process.env.IMAGESOUTDIR || path.join('assets', 'images', 'webp');
18
const STYLEOUTFILE = process.env.STYLEOUTFILE || 'styles.css';
19
const SQUASH = new RegExp('^[0-9]+-');
20
···
23
recursive: true,
24
force: true
25
})));
26
}
27
28
async function mkdir(dir: string | string[]) {
···
32
else {
33
await Promise.all(dir.map(mkdir));
34
}
35
}
36
37
function getFileExtension(filename: string) {
···
87
})));
88
}
89
90
// Process images
91
-
async function images(dir = '') {
92
-
const p = path.join(IMAGESDIR, dir);
93
-
await mkdir(p);
94
-
if (dir.length === 0) {
95
-
await mkdir(IMAGESOUTDIR)
96
-
await emptyDir(IMAGESOUTDIR);
97
}
98
-
const files = await fsp.readdir(p, {
99
-
withFileTypes: true
100
});
101
-
if (files.length) {
102
-
await Promise.all(files.map(f => new Promise(async (res, reject) => {
103
-
if (f.isFile()) {
104
-
const outDir = path.join(IMAGESOUTDIR, dir);
105
-
const infile = path.join(p, f.name);
106
-
const extension = getFileExtension(infile);
107
-
const outfile = path.join(outDir, f.name.substring(0, f.name.lastIndexOf('.')) + '.webp');
108
-
await mkdir(outDir);
109
-
console.log(`Processing image ${infile}`)
110
-
const libwebpArgs = ['-mt'];
111
-
if (extension === 'jpeg' || extension === 'jpg') {
112
-
libwebpArgs.push('-q', '60');
113
-
}
114
-
else {
115
-
libwebpArgs.push('-near_lossless', '55');
116
-
}
117
-
libwebpArgs.push(infile, '-o', outfile);
118
-
const proc = spawn('cwebp', libwebpArgs);
119
-
const timeout = setTimeout(() => {
120
-
reject('Timed out');
121
-
proc.kill();
122
-
}, parseInt(process.env['CWEBPTIMEOUT']) || 30000);
123
-
proc.on('exit', async (code) => {
124
-
clearTimeout(timeout);
125
-
if (code === 0) {
126
-
console.log(`Wrote ${outfile}`);
127
-
res(null);
128
-
}
129
-
else {
130
-
reject(code);
131
-
}
132
-
});
133
}
134
-
else if (f.isDirectory()) {
135
-
images(path.join(dir, f.name)).then(res).catch(reject);
136
}
137
-
})));
138
-
}
139
}
140
141
function isAbortError(err: unknown): boolean {
···
143
}
144
145
(async function () {
146
-
await Promise.all([styles(), scripts(), images()]);
147
if (process.argv.indexOf('--watch') >= 0) {
148
console.log('watching for changes...');
149
(async () => {
···
176
recursive: true // no Linux ☹️
177
});
178
for await (const _ of watcher)
179
-
await images();
180
} catch (err) {
181
if (isAbortError(err))
182
return;
···
11
const fsp = fs.promises;
12
const STYLESDIR = 'styles';
13
const SCRIPTSDIR = 'scripts';
14
+
const IMAGESDIR = path.join(process.cwd(), 'assets', 'images', 'original');
15
const STYLEOUTDIR = process.env.STYLEOUTDIR || path.join('assets', 'css');
16
const SCRIPTSOUTDIR = process.env.SCRIPTSOUTDIR || path.join('assets', 'js');
17
+
const WEBPOUTDIR = process.env.IMAGESOUTDIR || path.join('assets', 'images', 'webp');
18
+
const AVIFOUTDIR = process.env.IMAGESOUTDIR || path.join('assets', 'images', 'avif');
19
const STYLEOUTFILE = process.env.STYLEOUTFILE || 'styles.css';
20
const SQUASH = new RegExp('^[0-9]+-');
21
···
24
recursive: true,
25
force: true
26
})));
27
+
return true;
28
}
29
30
async function mkdir(dir: string | string[]) {
···
34
else {
35
await Promise.all(dir.map(mkdir));
36
}
37
+
return true;
38
}
39
40
function getFileExtension(filename: string) {
···
90
})));
91
}
92
93
+
async function getAllFiles(fullDir: string): Promise<string[]> {
94
+
if (!path.isAbsolute(fullDir)) {
95
+
throw new Error('path must be absolute');
96
+
}
97
+
const files: string[] = [];
98
+
const dirs = [''];
99
+
for (let i = 0; i < dirs.length; i++) {
100
+
const parent = dirs[i];
101
+
const dir = path.join(fullDir, parent);
102
+
const dirEnts = await fsp.readdir(dir, { withFileTypes: true });
103
+
dirEnts.forEach(de => (de.isDirectory() ? dirs : files).push(path.join(parent, de.name)));
104
+
}
105
+
return files;
106
+
}
107
+
108
// Process images
109
+
async function images(webp: boolean, avif: boolean, dir: string = IMAGESDIR) {
110
+
await mkdir(dir);
111
+
await mkdir(WEBPOUTDIR) && await emptyDir(WEBPOUTDIR);
112
+
await mkdir(AVIFOUTDIR) && await emptyDir(AVIFOUTDIR);
113
+
const releativeFiles = await getAllFiles(dir);
114
+
if (releativeFiles.length) {
115
+
await Promise.all(releativeFiles.map(f => processImage(dir, f, webp, avif)));
116
}
117
+
}
118
+
119
+
async function processImage(parentDir: string, relativeFile: string, webp: boolean, avif: boolean) {
120
+
const infile = path.join(parentDir, relativeFile);
121
+
const dir = path.dirname(relativeFile);
122
+
const outDirWebP = path.join(WEBPOUTDIR, dir);
123
+
const outDirAvif = path.join(AVIFOUTDIR, dir);
124
+
webp && await mkdir(outDirWebP);
125
+
avif && await mkdir(outDirAvif);
126
+
console.log(`Processing image ${infile}`);
127
+
webp && await convertWebP(infile, outDirWebP);
128
+
avif && await convertAvif(infile, outDirAvif);
129
+
}
130
+
131
+
function convertWebP(infile: string, outDir: string) {
132
+
return new Promise((resolve, reject) => {
133
+
const filename = path.basename(infile);
134
+
const extension = getFileExtension(filename);
135
+
const outfile = path.join(outDir, filename.substring(0, filename.lastIndexOf('.')) + '.webp');
136
+
const libwebpArgs = ['-mt'];
137
+
if (extension === 'jpeg' || extension === 'jpg') {
138
+
libwebpArgs.push('-q', '60');
139
+
}
140
+
else {
141
+
libwebpArgs.push('-near_lossless', '55');
142
+
}
143
+
libwebpArgs.push(infile, '-o', outfile);
144
+
const proc = spawn('cwebp', libwebpArgs);
145
+
const timeout = setTimeout(() => {
146
+
proc.kill();
147
+
reject(new Error(`process timed out`));
148
+
}, parseInt(process.env['CWEBPTIMEOUT']) || 30000);
149
+
proc.on('exit', async (code) => {
150
+
clearTimeout(timeout);
151
+
if (code === 0) {
152
+
console.log(`Wrote ${outfile}`);
153
+
resolve(true);
154
+
}
155
+
else {
156
+
reject(new Error(`process ended with code ${code}`));
157
+
}
158
+
});
159
});
160
+
}
161
+
162
+
function convertAvif(infile: string, outDir: string) {
163
+
return new Promise((resolve, reject) => {
164
+
const filename = path.basename(infile);
165
+
const extension = getFileExtension(filename);
166
+
const outfile = path.join(outDir, filename.substring(0, filename.lastIndexOf('.')) + '.avif');
167
+
const avifencArgs = '--speed 6 --jobs all --depth 8 --cicp 1/13/6 --codec aom'.split(' ');
168
+
if (extension === 'jpeg' || extension === 'jpg') {
169
+
avifencArgs.push('--advanced', 'cq-level=28', '-q', '40', '--yuv', '420');
170
+
}
171
+
else {
172
+
avifencArgs.push('--advanced', 'cq-level=30', '-q', '45', '--yuv', '444');
173
+
}
174
+
avifencArgs.push(infile, outfile);
175
+
console.log(`avifenc ${avifencArgs.join(' ')}`);
176
+
const proc = spawn('avifenc', avifencArgs);
177
+
const timeout = setTimeout(() => {
178
+
proc.kill();
179
+
reject(new Error(`process timed out`));
180
+
}, parseInt(process.env['AVIFENCTIMEOUT']) || 30000);
181
+
proc.on('exit', async (code) => {
182
+
clearTimeout(timeout);
183
+
if (code === 0) {
184
+
console.log(`Wrote ${outfile}`);
185
+
resolve(true);
186
}
187
+
else {
188
+
reject(new Error(`process ended with code ${code}`));
189
}
190
+
});
191
+
});
192
+
}
193
+
194
+
function commandExists(cmd: string): Promise<boolean> {
195
+
return new Promise((resolve, _) => {
196
+
const proc = spawn('which', cmd.split(' '));
197
+
proc.on('exit', async (code) => resolve(code === 0));
198
+
});
199
}
200
201
function isAbortError(err: unknown): boolean {
···
203
}
204
205
(async function () {
206
+
const webp = await commandExists('cwebp');
207
+
const avif = await commandExists('avifenc');
208
+
if (!webp && ! avif) {
209
+
console.error('WARNING: no image encoding software found.');
210
+
}
211
+
await Promise.all([styles(), scripts(), images(webp, avif)]);
212
if (process.argv.indexOf('--watch') >= 0) {
213
console.log('watching for changes...');
214
(async () => {
···
241
recursive: true // no Linux ☹️
242
});
243
for await (const _ of watcher)
244
+
await images(webp, avif);
245
} catch (err) {
246
if (isAbortError(err))
247
return;