UNPKG

next-image-export-optimizer

Version:

Optimizes all static images for Next.js static HTML export functionality

172 lines (171 loc) 8.77 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.downloadImagesInBatches = downloadImagesInBatches; const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const http = require("http"); const https = require("https"); const urlModule = require("url"); // Import url module to parse the url async function downloadImage(url, filename, folder) { return new Promise((resolve, reject) => { // Choose the right http library: const httpLib = urlModule.parse(url).protocol === "http:" ? http : https; const request = httpLib.get(url, function (response) { if (response.statusCode !== 200) { console.error(`Error: Unable to download ${url} (status code: ${response.statusCode}).`); reject(new Error(`Status code: ${response.statusCode}`)); return; } // check if the file is a valid image by checking the content type if (!response.headers["content-type"].startsWith("image/") && !response.headers["content-type"].startsWith("application/octet-stream")) { console.error(`Error: Unable to download ${url} (invalid content type: ${response.headers["content-type"]}).`); reject(new Error(`Invalid content type: ${response.headers["content-type"]}`)); return; } // Extract image format from response headers const contentType = response.headers["content-type"]; let imageFormat = contentType.split("/").pop(); // Further split on semicolon (;) if exists if (imageFormat.includes(";")) { imageFormat = imageFormat.split(";")[0]; } // Further split on plus (+) if exists, e.g. image/svg+xml if (imageFormat.includes("+")) { imageFormat = imageFormat.split("+")[0]; } // Check for jpeg and change it to jpg if necessary if (imageFormat === "jpeg") { imageFormat = "jpg"; } // Check if filename already has an extension that matches the image format const regex = new RegExp(`.${imageFormat}$`, "i"); const hasMatchingExtension = regex.test(filename); // Add appropriate extension to filename based on image format const formattedFilename = hasMatchingExtension ? filename : `${filename}.${imageFormat}`; fs_1.default.access(folder, fs_1.default.constants.W_OK, function (err) { if (err) { console.error(`Error: Unable to write to ${folder} (${err.message}).`); reject(err); return; } // on close, check the file size and reject if it's 0 otherwise resolve response .pipe(fs_1.default.createWriteStream(formattedFilename)) .on("error", function (err) { console.error(`Error: Unable to save ${formattedFilename} (${err.message}).`); reject(err); }) .on("close", function () { fs_1.default.stat(formattedFilename, function (err, stats) { if (err) { console.error(`Error: Unable to get the size of ${formattedFilename} (${err.message}).`); reject(err); return; } if (stats.size === 0) { console.error(`Error: Unable to save ${formattedFilename} (empty file).`); reject(new Error("Empty file")); return; } // to cache the image locally, we store a file with the same name as the image, but with a .lastUpdated extension and the timestamp storeLastUpdated({ basePath: folder, file: formattedFilename, dirPathWithoutBasePath: "", fullPath: formattedFilename, }); resolve(); }); }); }); }); request.on("error", (err) => { console.error(`Error: Unable to download ${url}.`, err); reject(err); }); }); } async function downloadImagesInBatches(imagesURLs, imageFileNames, folder, batchSize, remoteImageCacheTTL) { let downloadedImages = 0; let cachedImages = 0; const batches = Math.ceil(imagesURLs.length / batchSize); // determine the number of batches for (let i = 0; i < batches; i++) { const start = i * batchSize; // calculate the start index of the batch const end = Math.min(imagesURLs.length, start + batchSize); // calculate the end index of the batch const batchURLs = imagesURLs.slice(start, end); // slice the URLs for the current batch const batchFileNames = imageFileNames.slice(start, end); // slice the file names for the current batch for (let j = 0; j < batchFileNames.length; j++) { if (batchFileNames[j].fullPath === undefined) { console.error(`Error: Unable to download ${batchURLs[i]} (fullPath is undefined).`); return; } } const promises = batchURLs.map((url, index) => { const file = batchFileNames[index]; if (file.fullPath === undefined) { console.error(`Error: Unable to download ${url} (fullPath is undefined).`); return Promise.resolve(); } // check if the image was downloaded before and if it's still valid // if it's valid, skip downloading it again // if there is no .lastUpdated file, download the image // if there is a .lastUpdated file, check if it's older than the image // if it's older, download the image const lastUpdatedFilename = `${file.file}.lastUpdated`; const lastUpdatedPath = path_1.default.join(file.basePath, lastUpdatedFilename); let skipDownload = false; if (fs_1.default.existsSync(lastUpdatedPath) && fs_1.default.existsSync(file.fullPath)) { const lastUpdated = fs_1.default.readFileSync(lastUpdatedPath, "utf8"); const lastUpdatedTimestamp = parseInt(lastUpdated); const now = Date.now(); const timeDifferenceInSeconds = (now - lastUpdatedTimestamp) / 1000; if (timeDifferenceInSeconds < remoteImageCacheTTL) { // console.log( // `Skipping download of ${file.file} because it was downloaded ${timeDifferenceInSeconds} seconds ago.` // ); skipDownload = true; cachedImages++; } } else { // console.log("No .lastUpdated file found"); } if (skipDownload) { return Promise.resolve(); } downloadedImages++; return downloadImage(url, file.fullPath, folder); }); // create an array of promises for downloading images in the batch try { await Promise.all(promises); // download images in parallel for the current batch downloadedImages > 0 && console.log(`Downloaded ${downloadedImages} remote image${downloadedImages > 1 ? "s" : ""}.`); cachedImages > 0 && console.log(`Used ${cachedImages} cached remote image${cachedImages > 1 ? "s" : ""}.`); } catch (err) { console.error(`Error: Unable to download remote images (${err.message}).`); throw err; } } } const storeLastUpdated = (file) => { // to cache the image locally, we need to know last time it was downloaded // so we can check if it's still valid // store a file with the same name as the image, but with a .lastUpdated extension and the timestamp const lastUpdated = Date.now(); const lastUpdatedFilename = `${file.file}.lastUpdated`; try { fs_1.default.writeFileSync(lastUpdatedFilename, lastUpdated.toString()); } catch (error) { console.error(`Error writing the cache file for ${lastUpdatedFilename}: `, error); throw error; } };