image-optimizer.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.getHash = getHash;
  6. exports.detectContentType = detectContentType;
  7. exports.getMaxAge = getMaxAge;
  8. exports.imageOptimizer = imageOptimizer;
  9. exports.sendResponse = sendResponse;
  10. exports.resizeImage = resizeImage;
  11. exports.getImageSize = getImageSize;
  12. var _accept = require("next/dist/compiled/@hapi/accept");
  13. var _crypto = require("crypto");
  14. var _fs = require("fs");
  15. var _getOrientation = require("next/dist/compiled/get-orientation");
  16. var _imageSize = _interopRequireDefault(require("next/dist/compiled/image-size"));
  17. var _isAnimated = _interopRequireDefault(require("next/dist/compiled/is-animated"));
  18. var _contentDisposition = _interopRequireDefault(require("next/dist/compiled/content-disposition"));
  19. var _path = require("path");
  20. var _url = _interopRequireDefault(require("url"));
  21. var _main = require("./lib/squoosh/main");
  22. var _sendPayload = require("./send-payload");
  23. var _serveStatic = require("./serve-static");
  24. var _chalk = _interopRequireDefault(require("next/dist/compiled/chalk"));
  25. var _mockRequest = require("./lib/mock-request");
  26. var _matchRemotePattern = require("../shared/lib/match-remote-pattern");
  27. var _imageBlurSvg = require("../shared/lib/image-blur-svg");
  28. function _interopRequireDefault(obj) {
  29. return obj && obj.__esModule ? obj : {
  30. default: obj
  31. };
  32. }
  33. const AVIF = "image/avif";
  34. const WEBP = "image/webp";
  35. const PNG = "image/png";
  36. const JPEG = "image/jpeg";
  37. const GIF = "image/gif";
  38. const SVG = "image/svg+xml";
  39. const CACHE_VERSION = 3;
  40. const ANIMATABLE_TYPES = [
  41. WEBP,
  42. PNG,
  43. GIF
  44. ];
  45. const VECTOR_TYPES = [
  46. SVG
  47. ];
  48. const BLUR_IMG_SIZE = 8 // should match `next-image-loader`
  49. ;
  50. const BLUR_QUALITY = 70 // should match `next-image-loader`
  51. ;
  52. let sharp;
  53. try {
  54. sharp = require(process.env.NEXT_SHARP_PATH || "sharp");
  55. } catch (e) {
  56. // Sharp not present on the server, Squoosh fallback will be used
  57. }
  58. let showSharpMissingWarning = process.env.NODE_ENV === "production";
  59. function getSupportedMimeType(options, accept = "") {
  60. const mimeType = (0, _accept).mediaType(accept, options);
  61. return accept.includes(mimeType) ? mimeType : "";
  62. }
  63. function getHash(items) {
  64. const hash = (0, _crypto).createHash("sha256");
  65. for (let item of items){
  66. if (typeof item === "number") hash.update(String(item));
  67. else {
  68. hash.update(item);
  69. }
  70. }
  71. // See https://en.wikipedia.org/wiki/Base64#Filenames
  72. return hash.digest("base64").replace(/\//g, "-");
  73. }
  74. async function writeToCacheDir(dir, extension, maxAge, expireAt, buffer, etag) {
  75. const filename = (0, _path).join(dir, `${maxAge}.${expireAt}.${etag}.${extension}`);
  76. // Added in: v14.14.0 https://nodejs.org/api/fs.html#fspromisesrmpath-options
  77. // attempt cleaning up existing stale cache
  78. if (_fs.promises.rm) {
  79. await _fs.promises.rm(dir, {
  80. force: true,
  81. recursive: true
  82. }).catch(()=>{});
  83. } else {
  84. await _fs.promises.rmdir(dir, {
  85. recursive: true
  86. }).catch(()=>{});
  87. }
  88. await _fs.promises.mkdir(dir, {
  89. recursive: true
  90. });
  91. await _fs.promises.writeFile(filename, buffer);
  92. }
  93. function detectContentType(buffer) {
  94. if ([
  95. 0xff,
  96. 0xd8,
  97. 0xff
  98. ].every((b, i)=>buffer[i] === b)) {
  99. return JPEG;
  100. }
  101. if ([
  102. 0x89,
  103. 0x50,
  104. 0x4e,
  105. 0x47,
  106. 0x0d,
  107. 0x0a,
  108. 0x1a,
  109. 0x0a
  110. ].every((b, i)=>buffer[i] === b)) {
  111. return PNG;
  112. }
  113. if ([
  114. 0x47,
  115. 0x49,
  116. 0x46,
  117. 0x38
  118. ].every((b, i)=>buffer[i] === b)) {
  119. return GIF;
  120. }
  121. if ([
  122. 0x52,
  123. 0x49,
  124. 0x46,
  125. 0x46,
  126. 0,
  127. 0,
  128. 0,
  129. 0,
  130. 0x57,
  131. 0x45,
  132. 0x42,
  133. 0x50
  134. ].every((b, i)=>!b || buffer[i] === b)) {
  135. return WEBP;
  136. }
  137. if ([
  138. 0x3c,
  139. 0x3f,
  140. 0x78,
  141. 0x6d,
  142. 0x6c
  143. ].every((b, i)=>buffer[i] === b)) {
  144. return SVG;
  145. }
  146. if ([
  147. 0,
  148. 0,
  149. 0,
  150. 0,
  151. 0x66,
  152. 0x74,
  153. 0x79,
  154. 0x70,
  155. 0x61,
  156. 0x76,
  157. 0x69,
  158. 0x66
  159. ].every((b, i)=>!b || buffer[i] === b)) {
  160. return AVIF;
  161. }
  162. return null;
  163. }
  164. class ImageOptimizerCache {
  165. static validateParams(req, query, nextConfig, isDev) {
  166. var ref;
  167. const imageData = nextConfig.images;
  168. const { deviceSizes =[] , imageSizes =[] , domains =[] , minimumCacheTTL =60 , formats =[
  169. "image/webp"
  170. ] , } = imageData;
  171. const remotePatterns = ((ref = nextConfig.images) == null ? void 0 : ref.remotePatterns) || [];
  172. const { url , w , q } = query;
  173. let href;
  174. if (!url) {
  175. return {
  176. errorMessage: '"url" parameter is required'
  177. };
  178. } else if (Array.isArray(url)) {
  179. return {
  180. errorMessage: '"url" parameter cannot be an array'
  181. };
  182. }
  183. let isAbsolute;
  184. if (url.startsWith("/")) {
  185. href = url;
  186. isAbsolute = false;
  187. } else {
  188. let hrefParsed;
  189. try {
  190. hrefParsed = new URL(url);
  191. href = hrefParsed.toString();
  192. isAbsolute = true;
  193. } catch (_error) {
  194. return {
  195. errorMessage: '"url" parameter is invalid'
  196. };
  197. }
  198. if (![
  199. "http:",
  200. "https:"
  201. ].includes(hrefParsed.protocol)) {
  202. return {
  203. errorMessage: '"url" parameter is invalid'
  204. };
  205. }
  206. if (!(0, _matchRemotePattern).hasMatch(domains, remotePatterns, hrefParsed)) {
  207. return {
  208. errorMessage: '"url" parameter is not allowed'
  209. };
  210. }
  211. }
  212. if (!w) {
  213. return {
  214. errorMessage: '"w" parameter (width) is required'
  215. };
  216. } else if (Array.isArray(w)) {
  217. return {
  218. errorMessage: '"w" parameter (width) cannot be an array'
  219. };
  220. }
  221. if (!q) {
  222. return {
  223. errorMessage: '"q" parameter (quality) is required'
  224. };
  225. } else if (Array.isArray(q)) {
  226. return {
  227. errorMessage: '"q" parameter (quality) cannot be an array'
  228. };
  229. }
  230. const width = parseInt(w, 10);
  231. if (width <= 0 || isNaN(width)) {
  232. return {
  233. errorMessage: '"w" parameter (width) must be a number greater than 0'
  234. };
  235. }
  236. const sizes = [
  237. ...deviceSizes || [],
  238. ...imageSizes || []
  239. ];
  240. if (isDev) {
  241. sizes.push(BLUR_IMG_SIZE);
  242. }
  243. const isValidSize = sizes.includes(width) || isDev && width <= BLUR_IMG_SIZE;
  244. if (!isValidSize) {
  245. return {
  246. errorMessage: `"w" parameter (width) of ${width} is not allowed`
  247. };
  248. }
  249. const quality = parseInt(q);
  250. if (isNaN(quality) || quality < 1 || quality > 100) {
  251. return {
  252. errorMessage: '"q" parameter (quality) must be a number between 1 and 100'
  253. };
  254. }
  255. const mimeType = getSupportedMimeType(formats || [], req.headers["accept"]);
  256. const isStatic = url.startsWith(`${nextConfig.basePath || ""}/_next/static/media`);
  257. return {
  258. href,
  259. sizes,
  260. isAbsolute,
  261. isStatic,
  262. width,
  263. quality,
  264. mimeType,
  265. minimumCacheTTL
  266. };
  267. }
  268. static getCacheKey({ href , width , quality , mimeType }) {
  269. return getHash([
  270. CACHE_VERSION,
  271. href,
  272. width,
  273. quality,
  274. mimeType
  275. ]);
  276. }
  277. constructor({ distDir , nextConfig }){
  278. this.cacheDir = (0, _path).join(distDir, "cache", "images");
  279. this.nextConfig = nextConfig;
  280. }
  281. async get(cacheKey) {
  282. try {
  283. const cacheDir = (0, _path).join(this.cacheDir, cacheKey);
  284. const files = await _fs.promises.readdir(cacheDir);
  285. const now = Date.now();
  286. for (const file of files){
  287. const [maxAgeSt, expireAtSt, etag, extension] = file.split(".");
  288. const buffer = await _fs.promises.readFile((0, _path).join(cacheDir, file));
  289. const expireAt = Number(expireAtSt);
  290. const maxAge = Number(maxAgeSt);
  291. return {
  292. value: {
  293. kind: "IMAGE",
  294. etag,
  295. buffer,
  296. extension
  297. },
  298. revalidateAfter: Math.max(maxAge, this.nextConfig.images.minimumCacheTTL) * 1000 + Date.now(),
  299. curRevalidate: maxAge,
  300. isStale: now > expireAt
  301. };
  302. }
  303. } catch (_) {
  304. // failed to read from cache dir, treat as cache miss
  305. }
  306. return null;
  307. }
  308. async set(cacheKey, value, revalidate) {
  309. if ((value == null ? void 0 : value.kind) !== "IMAGE") {
  310. throw new Error("invariant attempted to set non-image to image-cache");
  311. }
  312. if (typeof revalidate !== "number") {
  313. throw new Error("invariant revalidate must be a number for image-cache");
  314. }
  315. const expireAt = Math.max(revalidate, this.nextConfig.images.minimumCacheTTL) * 1000 + Date.now();
  316. try {
  317. await writeToCacheDir((0, _path).join(this.cacheDir, cacheKey), value.extension, revalidate, expireAt, value.buffer, value.etag);
  318. } catch (err) {
  319. console.error(`Failed to write image to cache ${cacheKey}`, err);
  320. }
  321. }
  322. }
  323. exports.ImageOptimizerCache = ImageOptimizerCache;
  324. class ImageError extends Error {
  325. constructor(statusCode, message){
  326. super(message);
  327. // ensure an error status is used > 400
  328. if (statusCode >= 400) {
  329. this.statusCode = statusCode;
  330. } else {
  331. this.statusCode = 500;
  332. }
  333. }
  334. }
  335. exports.ImageError = ImageError;
  336. function parseCacheControl(str) {
  337. const map = new Map();
  338. if (!str) {
  339. return map;
  340. }
  341. for (let directive of str.split(",")){
  342. let [key, value] = directive.trim().split("=");
  343. key = key.toLowerCase();
  344. if (value) {
  345. value = value.toLowerCase();
  346. }
  347. map.set(key, value);
  348. }
  349. return map;
  350. }
  351. function getMaxAge(str) {
  352. const map = parseCacheControl(str);
  353. if (map) {
  354. let age = map.get("s-maxage") || map.get("max-age") || "";
  355. if (age.startsWith('"') && age.endsWith('"')) {
  356. age = age.slice(1, -1);
  357. }
  358. const n = parseInt(age, 10);
  359. if (!isNaN(n)) {
  360. return n;
  361. }
  362. }
  363. return 0;
  364. }
  365. async function imageOptimizer(_req, _res, paramsResult, nextConfig, isDev, handleRequest) {
  366. let upstreamBuffer;
  367. let upstreamType;
  368. let maxAge;
  369. const { isAbsolute , href , width , mimeType , quality } = paramsResult;
  370. if (isAbsolute) {
  371. const upstreamRes = await fetch(href);
  372. if (!upstreamRes.ok) {
  373. console.error("upstream image response failed for", href, upstreamRes.status);
  374. throw new ImageError(upstreamRes.status, '"url" parameter is valid but upstream response is invalid');
  375. }
  376. upstreamBuffer = Buffer.from(await upstreamRes.arrayBuffer());
  377. upstreamType = detectContentType(upstreamBuffer) || upstreamRes.headers.get("Content-Type");
  378. maxAge = getMaxAge(upstreamRes.headers.get("Cache-Control"));
  379. } else {
  380. try {
  381. const { resBuffers , req: mockReq , res: mockRes , streamPromise: isStreamFinished , } = (0, _mockRequest).mockRequest(href, _req.headers, _req.method || "GET", _req.connection);
  382. await handleRequest(mockReq, mockRes, _url.default.parse(href, true));
  383. await isStreamFinished;
  384. if (!mockRes.statusCode) {
  385. console.error("image response failed for", href, mockRes.statusCode);
  386. throw new ImageError(mockRes.statusCode, '"url" parameter is valid but internal response is invalid');
  387. }
  388. upstreamBuffer = Buffer.concat(resBuffers);
  389. upstreamType = detectContentType(upstreamBuffer) || mockRes.getHeader("Content-Type");
  390. maxAge = getMaxAge(mockRes.getHeader("Cache-Control"));
  391. } catch (err) {
  392. console.error("upstream image response failed for", href, err);
  393. throw new ImageError(500, '"url" parameter is valid but upstream response is invalid');
  394. }
  395. }
  396. if (upstreamType === SVG && !nextConfig.images.dangerouslyAllowSVG) {
  397. console.error(`The requested resource "${href}" has type "${upstreamType}" but dangerouslyAllowSVG is disabled`);
  398. throw new ImageError(400, '"url" parameter is valid but image type is not allowed');
  399. }
  400. if (upstreamType) {
  401. const vector = VECTOR_TYPES.includes(upstreamType);
  402. const animate = ANIMATABLE_TYPES.includes(upstreamType) && (0, _isAnimated).default(upstreamBuffer);
  403. if (vector || animate) {
  404. return {
  405. buffer: upstreamBuffer,
  406. contentType: upstreamType,
  407. maxAge
  408. };
  409. }
  410. if (!upstreamType.startsWith("image/")) {
  411. console.error("The requested resource isn't a valid image for", href, "received", upstreamType);
  412. throw new ImageError(400, "The requested resource isn't a valid image.");
  413. }
  414. }
  415. let contentType;
  416. if (mimeType) {
  417. contentType = mimeType;
  418. } else if ((upstreamType == null ? void 0 : upstreamType.startsWith("image/")) && (0, _serveStatic).getExtension(upstreamType) && upstreamType !== WEBP && upstreamType !== AVIF) {
  419. contentType = upstreamType;
  420. } else {
  421. contentType = JPEG;
  422. }
  423. try {
  424. let optimizedBuffer;
  425. if (sharp) {
  426. // Begin sharp transformation logic
  427. const transformer = sharp(upstreamBuffer);
  428. transformer.rotate();
  429. const { width: metaWidth } = await transformer.metadata();
  430. if (metaWidth && metaWidth > width) {
  431. transformer.resize(width);
  432. }
  433. if (contentType === AVIF) {
  434. if (transformer.avif) {
  435. const avifQuality = quality - 15;
  436. transformer.avif({
  437. quality: Math.max(avifQuality, 0),
  438. chromaSubsampling: "4:2:0"
  439. });
  440. } else {
  441. console.warn(_chalk.default.yellow.bold("Warning: ") + `Your installed version of the 'sharp' package does not support AVIF images. Run 'yarn add sharp@latest' to upgrade to the latest version.\n` + "Read more: https://nextjs.org/docs/messages/sharp-version-avif");
  442. transformer.webp({
  443. quality
  444. });
  445. }
  446. } else if (contentType === WEBP) {
  447. transformer.webp({
  448. quality
  449. });
  450. } else if (contentType === PNG) {
  451. transformer.png({
  452. quality
  453. });
  454. } else if (contentType === JPEG) {
  455. transformer.jpeg({
  456. quality
  457. });
  458. }
  459. optimizedBuffer = await transformer.toBuffer();
  460. // End sharp transformation logic
  461. } else {
  462. if (showSharpMissingWarning && nextConfig.output === "standalone") {
  463. // TODO: should we ensure squoosh also works even though we don't
  464. // recommend it be used in production and this is a production feature
  465. console.error(`Error: 'sharp' is required to be installed in standalone mode for the image optimization to function correctly`);
  466. throw new ImageError(500, "internal server error");
  467. }
  468. // Show sharp warning in production once
  469. if (showSharpMissingWarning) {
  470. console.warn(_chalk.default.yellow.bold("Warning: ") + `For production Image Optimization with Next.js, the optional 'sharp' package is strongly recommended. Run 'yarn add sharp', and Next.js will use it automatically for Image Optimization.\n` + "Read more: https://nextjs.org/docs/messages/sharp-missing-in-production");
  471. showSharpMissingWarning = false;
  472. }
  473. // Begin Squoosh transformation logic
  474. const orientation = await (0, _getOrientation).getOrientation(upstreamBuffer);
  475. const operations = [];
  476. if (orientation === _getOrientation.Orientation.RIGHT_TOP) {
  477. operations.push({
  478. type: "rotate",
  479. numRotations: 1
  480. });
  481. } else if (orientation === _getOrientation.Orientation.BOTTOM_RIGHT) {
  482. operations.push({
  483. type: "rotate",
  484. numRotations: 2
  485. });
  486. } else if (orientation === _getOrientation.Orientation.LEFT_BOTTOM) {
  487. operations.push({
  488. type: "rotate",
  489. numRotations: 3
  490. });
  491. } else {
  492. // TODO: support more orientations
  493. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  494. // const _: never = orientation
  495. }
  496. operations.push({
  497. type: "resize",
  498. width
  499. });
  500. if (contentType === AVIF) {
  501. optimizedBuffer = await (0, _main).processBuffer(upstreamBuffer, operations, "avif", quality);
  502. } else if (contentType === WEBP) {
  503. optimizedBuffer = await (0, _main).processBuffer(upstreamBuffer, operations, "webp", quality);
  504. } else if (contentType === PNG) {
  505. optimizedBuffer = await (0, _main).processBuffer(upstreamBuffer, operations, "png", quality);
  506. } else if (contentType === JPEG) {
  507. optimizedBuffer = await (0, _main).processBuffer(upstreamBuffer, operations, "jpeg", quality);
  508. }
  509. // End Squoosh transformation logic
  510. }
  511. if (optimizedBuffer) {
  512. if (isDev && width <= BLUR_IMG_SIZE && quality === BLUR_QUALITY) {
  513. // During `next dev`, we don't want to generate blur placeholders with webpack
  514. // because it can delay starting the dev server. Instead, `next-image-loader.js`
  515. // will inline a special url to lazily generate the blur placeholder at request time.
  516. const meta = await (0, _main).getMetadata(optimizedBuffer);
  517. const opts = {
  518. blurWidth: meta.width,
  519. blurHeight: meta.height,
  520. blurDataURL: `data:${contentType};base64,${optimizedBuffer.toString("base64")}`
  521. };
  522. optimizedBuffer = Buffer.from(unescape((0, _imageBlurSvg).getImageBlurSvg(opts)));
  523. contentType = "image/svg+xml";
  524. }
  525. return {
  526. buffer: optimizedBuffer,
  527. contentType,
  528. maxAge: Math.max(maxAge, nextConfig.images.minimumCacheTTL)
  529. };
  530. } else {
  531. throw new ImageError(500, "Unable to optimize buffer");
  532. }
  533. } catch (error) {
  534. if (upstreamBuffer && upstreamType) {
  535. // If we fail to optimize, fallback to the original image
  536. return {
  537. buffer: upstreamBuffer,
  538. contentType: upstreamType,
  539. maxAge: nextConfig.images.minimumCacheTTL
  540. };
  541. } else {
  542. throw new ImageError(400, "Unable to optimize image and unable to fallback to upstream image");
  543. }
  544. }
  545. }
  546. function getFileNameWithExtension(url, contentType) {
  547. const [urlWithoutQueryParams] = url.split("?");
  548. const fileNameWithExtension = urlWithoutQueryParams.split("/").pop();
  549. if (!contentType || !fileNameWithExtension) {
  550. return;
  551. }
  552. const [fileName] = fileNameWithExtension.split(".");
  553. const extension = (0, _serveStatic).getExtension(contentType);
  554. return `${fileName}.${extension}`;
  555. }
  556. function setResponseHeaders(req, res, url, etag, contentType, isStatic, xCache, contentSecurityPolicy, maxAge, isDev) {
  557. res.setHeader("Vary", "Accept");
  558. res.setHeader("Cache-Control", isStatic ? "public, max-age=315360000, immutable" : `public, max-age=${isDev ? 0 : maxAge}, must-revalidate`);
  559. if ((0, _sendPayload).sendEtagResponse(req, res, etag)) {
  560. // already called res.end() so we're finished
  561. return {
  562. finished: true
  563. };
  564. }
  565. if (contentType) {
  566. res.setHeader("Content-Type", contentType);
  567. }
  568. const fileName = getFileNameWithExtension(url, contentType);
  569. if (fileName) {
  570. res.setHeader("Content-Disposition", (0, _contentDisposition).default(fileName, {
  571. type: "inline"
  572. }));
  573. }
  574. if (contentSecurityPolicy) {
  575. res.setHeader("Content-Security-Policy", contentSecurityPolicy);
  576. }
  577. res.setHeader("X-Nextjs-Cache", xCache);
  578. return {
  579. finished: false
  580. };
  581. }
  582. function sendResponse(req, res, url, extension, buffer, isStatic, xCache, contentSecurityPolicy, maxAge, isDev) {
  583. const contentType = (0, _serveStatic).getContentType(extension);
  584. const etag = getHash([
  585. buffer
  586. ]);
  587. const result = setResponseHeaders(req, res, url, etag, contentType, isStatic, xCache, contentSecurityPolicy, maxAge, isDev);
  588. if (!result.finished) {
  589. res.setHeader("Content-Length", Buffer.byteLength(buffer));
  590. res.end(buffer);
  591. }
  592. }
  593. async function resizeImage(content, width, height, // Should match VALID_BLUR_EXT
  594. extension, quality) {
  595. if ((0, _isAnimated).default(content)) {
  596. return content;
  597. } else if (sharp) {
  598. const transformer = sharp(content);
  599. if (extension === "avif") {
  600. if (transformer.avif) {
  601. transformer.avif({
  602. quality
  603. });
  604. } else {
  605. console.warn(_chalk.default.yellow.bold("Warning: ") + `Your installed version of the 'sharp' package does not support AVIF images. Run 'yarn add sharp@latest' to upgrade to the latest version.\n` + "Read more: https://nextjs.org/docs/messages/sharp-version-avif");
  606. transformer.webp({
  607. quality
  608. });
  609. }
  610. } else if (extension === "webp") {
  611. transformer.webp({
  612. quality
  613. });
  614. } else if (extension === "png") {
  615. transformer.png({
  616. quality
  617. });
  618. } else if (extension === "jpeg") {
  619. transformer.jpeg({
  620. quality
  621. });
  622. }
  623. transformer.resize(width, height);
  624. const buf = await transformer.toBuffer();
  625. return buf;
  626. } else {
  627. const resizeOperationOpts = {
  628. type: "resize",
  629. width,
  630. height
  631. };
  632. const buf = await (0, _main).processBuffer(content, [
  633. resizeOperationOpts
  634. ], extension, quality);
  635. return buf;
  636. }
  637. }
  638. async function getImageSize(buffer, // Should match VALID_BLUR_EXT
  639. extension) {
  640. // TODO: upgrade "image-size" package to support AVIF
  641. // See https://github.com/image-size/image-size/issues/348
  642. if (extension === "avif") {
  643. if (sharp) {
  644. const transformer = sharp(buffer);
  645. const { width , height } = await transformer.metadata();
  646. return {
  647. width,
  648. height
  649. };
  650. } else {
  651. const { width , height } = await (0, _main).decodeBuffer(buffer);
  652. return {
  653. width,
  654. height
  655. };
  656. }
  657. }
  658. const { width , height } = (0, _imageSize).default(buffer);
  659. return {
  660. width,
  661. height
  662. };
  663. }
  664. class Deferred {
  665. constructor(){
  666. this.promise = new Promise((resolve, reject)=>{
  667. this.resolve = resolve;
  668. this.reject = reject;
  669. });
  670. }
  671. }
  672. exports.Deferred = Deferred;
  673. //# sourceMappingURL=image-optimizer.js.map