-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
/
ssg.ts
133 lines (112 loc) · 3.74 KB
/
ssg.ts
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
133
import { doWork } from '@altano/tiny-async-pool';
import type { AstroConfig } from 'astro';
import { bgGreen, black, cyan, dim, green } from 'kleur/colors';
import fs from 'node:fs/promises';
import OS from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { SSRImageService, TransformOptions } from '../loaders/index.js';
import { debug, info, LoggerLevel, warn } from '../utils/logger.js';
import { isRemoteImage } from '../utils/paths.js';
async function loadLocalImage(src: string | URL) {
try {
return await fs.readFile(src);
} catch {
return undefined;
}
}
async function loadRemoteImage(src: string) {
try {
const res = await fetch(src);
if (!res.ok) {
return undefined;
}
return Buffer.from(await res.arrayBuffer());
} catch {
return undefined;
}
}
function getTimeStat(timeStart: number, timeEnd: number) {
const buildTime = timeEnd - timeStart;
return buildTime < 750 ? `${Math.round(buildTime)}ms` : `${(buildTime / 1000).toFixed(2)}s`;
}
export interface SSGBuildParams {
loader: SSRImageService;
staticImages: Map<string, Map<string, TransformOptions>>;
config: AstroConfig;
outDir: URL;
logLevel: LoggerLevel;
}
export async function ssgBuild({ loader, staticImages, config, outDir, logLevel }: SSGBuildParams) {
const timer = performance.now();
const cpuCount = OS.cpus().length;
info({
level: logLevel,
prefix: false,
message: `${bgGreen(
black(
` optimizing ${staticImages.size} image${
staticImages.size > 1 ? 's' : ''
} in batches of ${cpuCount} `
)
)}`,
});
async function processStaticImage([src, transformsMap]: [
string,
Map<string, TransformOptions>
]): Promise<void> {
let inputFile: string | undefined = undefined;
let inputBuffer: Buffer | undefined = undefined;
// Vite will prefix a hashed image with the base path, we need to strip this
// off to find the actual file relative to /dist
if (config.base && src.startsWith(config.base)) {
src = src.substring(config.base.length - 1);
}
if (isRemoteImage(src)) {
// try to load the remote image
inputBuffer = await loadRemoteImage(src);
} else {
const inputFileURL = new URL(`.${src}`, outDir);
inputFile = fileURLToPath(inputFileURL);
inputBuffer = await loadLocalImage(inputFile);
}
if (!inputBuffer) {
// eslint-disable-next-line no-console
warn({ level: logLevel, message: `"${src}" image could not be fetched` });
return;
}
const transforms = Array.from(transformsMap.entries());
debug({ level: logLevel, prefix: false, message: `${green('▶')} transforming ${src}` });
let timeStart = performance.now();
// process each transformed version
for (const [filename, transform] of transforms) {
timeStart = performance.now();
let outputFile: string;
if (isRemoteImage(src)) {
const outputFileURL = new URL(path.join('./assets', path.basename(filename)), outDir);
outputFile = fileURLToPath(outputFileURL);
} else {
const outputFileURL = new URL(path.join('./assets', filename), outDir);
outputFile = fileURLToPath(outputFileURL);
}
const { data } = await loader.transform(inputBuffer, transform);
await fs.writeFile(outputFile, data);
const timeEnd = performance.now();
const timeChange = getTimeStat(timeStart, timeEnd);
const timeIncrease = `(+${timeChange})`;
const pathRelative = outputFile.replace(fileURLToPath(outDir), '');
debug({
level: logLevel,
prefix: false,
message: ` ${cyan('created')} ${dim(pathRelative)} ${dim(timeIncrease)}`,
});
}
}
// transform each original image file in batches
await doWork(cpuCount, staticImages, processStaticImage);
info({
level: logLevel,
prefix: false,
message: dim(`Completed in ${getTimeStat(timer, performance.now())}.\n`),
});
}