端圖片壓縮指南:基于 Sharp 的 @onlook/image-server 實(shí)踐與源碼解析)
Onlook 服務(wù)端圖片壓縮指南基于 Sharp 的 onlook/image-server 實(shí)踐與源碼解析【免費(fèi)下載鏈接】onlookThe Cursor for Designers ? An Open-Source AI-First Design tool ? Visually build, style, and edit your React App with AI項(xiàng)目地址: https://gitcode.com/GitHub_Trending/on/onlook本篇技術(shù)指南圍繞 Onlook 開源倉(cāng)庫(kù)中的packages/image-server包展開它是一套僅限服務(wù)端運(yùn)行的圖片壓縮工具集底層基于 Sharp^0.33.5實(shí)現(xiàn)。文章會(huì)完整覆蓋該包的安裝方式、兩個(gè)核心 APIcompressImageServer與batchCompressImagesServer的調(diào)用方法、全部壓縮參數(shù)與默認(rèn)值、格式支持/跳過規(guī)則、內(nèi)置壓縮預(yù)設(shè)并結(jié)合 compress.ts 源碼、image.test.ts 測(cè)試用例以及 image.ts 中的真實(shí) tRPC 集成示例說明它在 Onlook 實(shí)際項(xiàng)目里的落地方式。讀完本文你將能獨(dú)立在任意 Node.js 服務(wù)API 路由、服務(wù)端函數(shù)、批處理腳本中完成圖片壓縮、格式轉(zhuǎn)換、縮放與批量處理并理解其設(shè)計(jì)邊界。一、包定位與使用紅線只能在服務(wù)端使用onlook/image-server在package.json中的描述是 Server-side image processing utilities for Onlook其唯一運(yùn)行時(shí)依賴是sharp^0.33.5并要求node 18.0.0。Sharp 是典型的 Node.js 原生模塊依賴 libvips 原生庫(kù)因此該包絕對(duì)不能在瀏覽器或 Electron preload 腳本中導(dǎo)入?安全使用場(chǎng)景Node.js 服務(wù)器、API 路由如 Next.js Route Handlers / Server Actions、服務(wù)端函數(shù)、批處理腳本?禁止使用場(chǎng)景瀏覽器端代碼、Electron preload 腳本、客戶端組件這一約束不僅寫在 README 開頭也以注釋形式固化在包的入口文件packages/image-server/src/index.ts第一行// ?? WARNING: This package contains Node.js-only dependencies (Sharp). Do not use in a browser environment. export * from ./compress; export * from ./types;從源碼結(jié)構(gòu)看入口只做兩件事導(dǎo)出壓縮實(shí)現(xiàn)compress.ts與共享類型types.ts。這意味著你在任何import { compressImageServer } from onlook/image-server的地方都應(yīng)先確認(rèn)該模塊運(yùn)行在服務(wù)端進(jìn)程內(nèi)。二、安裝方式該包是 Onlook monorepo使用 Bun 管理內(nèi)部包需要服務(wù)端圖片處理能力的其他包直接在dependencies中聲明即可{ dependencies: { onlook/image-server: * } }安裝后可從包入口獲得以下導(dǎo)出compressImageServer單張圖片壓縮batchCompressImagesServer批量壓縮CompressionOptions/CompressionResult/SupportedFormat類型定義詳見后文三、快速上手兩個(gè)核心 API3.1 單圖壓縮compressImageServer(input, outputPath?, options?)簽名compress.tsexport async function compressImageServer( input: string | Buffer, outputPath?: string, options: CompressionOptions {}, ): PromiseCompressionResultinputstring | Buffer—— 文件路徑或圖片 BufferBuffer 輸入在服務(wù)端接收上傳、tRPC 傳參等場(chǎng)景下非常實(shí)用outputPath可選。提供則壓縮結(jié)果寫入該文件不提供則返回內(nèi)存 Bufferoptions可選壓縮配置完整參數(shù)見第四節(jié)典型用法一壓縮并保存為 WebP 文件。import { compressImageServer } from onlook/image-server; // Compress and save to file const result await compressImageServer(input.jpg, output.webp, { quality: 80, format: webp, });典型用法二壓縮到內(nèi)存 Buffer適合后續(xù)上傳、入庫(kù)或經(jīng)接口返回。// Compress to buffer const result await compressImageServer(input.jpg, undefined, { quality: 70 });當(dāng)outputPath缺省時(shí)實(shí)現(xiàn)走toBuffer({ resolveWithObject: true })分支result.buffer即為壓縮后的數(shù)據(jù)見 compress.ts。3.2 批量壓縮batchCompressImagesServer(inputPaths, outputDir, options?)簽名compress.tsexport async function batchCompressImagesServer( inputPaths: string[], outputDir: string, options: CompressionOptions {}, ): PromiseCompressionResult[]import { batchCompressImagesServer } from onlook/image-server; const results await batchCompressImagesServer( [image1.jpg, image2.png], ./output-directory, { format: webp, quality: 85 }, );實(shí)現(xiàn)細(xì)節(jié)可從源碼確認(rèn)先f(wàn)s.mkdir(outputDir, { recursive: true })確保輸出目錄存在過濾掉.ico/.svg路徑并為每個(gè)被跳過的文件在結(jié)果數(shù)組中插入一條success: false的跳過記錄保證返回結(jié)果數(shù)量與輸入數(shù)量一一對(duì)應(yīng)剩余文件通過Promise.all并行調(diào)用compressImageServer輸出文件名為原名去擴(kuò)展名.輸出格式例如photo.jpg→photo.webp見 compress.ts。注意批量模式下若format缺省或?yàn)閍uto統(tǒng)一按webp輸出compress.ts與單圖壓縮的“自動(dòng)推導(dǎo)原格式”策略不同這是批量場(chǎng)景為了統(tǒng)一輸出格式而做的取舍。四、完整參數(shù)表與默認(rèn)值CompressionOptions定義在 types.tscompressImageServer的解構(gòu)默認(rèn)值位于 compress.ts兩者合并后如下參數(shù)類型默認(rèn)值說明qualitynumber80有損格式質(zhì)量JPEG/WebP/AVIF 適用0–100widthnumber未設(shè)置縮放目標(biāo)寬度與height至少提供一個(gè)才觸發(fā)縮放heightnumber未設(shè)置縮放目標(biāo)高度f(wàn)ormatSupportedFormat \| autoauto輸出格式j(luò)peg/png/webp/avifauto按輸入自動(dòng)推導(dǎo)progressivebooleantrue漸進(jìn)式編碼JPEG/PNG 適用mozjpegbooleantrue是否使用 mozjpeg 編碼器JPEG 適用effortnumber4編碼努力程度WebP/AVIF 適用越高壓縮率越好、耗時(shí)越長(zhǎng)compressionLevelnumber6PNG 壓縮級(jí)別0–9keepAspectRatiobooleantrue縮放時(shí)是否保持寬高比。true用sharp.fit.insidefalse用sharp.fit.fill可能拉伸變形withoutEnlargementbooleantrue原圖小于目標(biāo)尺寸時(shí)不做放大SupportedFormat僅包含四種輸出格式types.tsexport type SupportedFormat jpeg | png | webp | avif;4.1 縮放邏輯當(dāng)width或height存在時(shí)源碼會(huì)構(gòu)造 resize 參數(shù)const resizeOptions { width, height, fit: keepAspectRatio ? sharp.fit.inside : sharp.fit.fill, withoutEnlargement, }; sharpInstance sharpInstance.resize(resizeOptions);keepAspectRatio: truefit: inside等比縮放結(jié)果不會(huì)超過目標(biāo)矩形適合縮略圖場(chǎng)景keepAspectRatio: falsefit: fill強(qiáng)制填滿目標(biāo)尺寸可能改變寬高比。4.2 按格式分派的壓縮參數(shù)applyFormatCompressioncompress.ts把統(tǒng)一選項(xiàng)映射到各格式的 Sharp 編碼參數(shù)輸出格式使用的 Sharp 選項(xiàng)生效參數(shù)jpeg.jpeg({ quality, progressive, mozjpeg })質(zhì)量、漸進(jìn)式、mozjpegpng.png({ compressionLevel, progressive })壓縮級(jí)別、漸進(jìn)式webp.webp({ quality, effort })質(zhì)量、努力程度avif.avif({ quality, effort })質(zhì)量、努力程度兜底.webp({ quality, effort })同 WebP4.3auto格式的推導(dǎo)規(guī)則determineOptimalFormatcompress.ts根據(jù)輸入圖片的元數(shù)據(jù)格式而非僅憑擴(kuò)展名決定輸出格式輸入格式輸出格式理由jpeg/jpgjpeg保持照片格式pngpng保留透明通道與無(wú)損特性gifwebp動(dòng)圖轉(zhuǎn)靜圖壓縮率更好tiff/tifjpeg高保真源轉(zhuǎn)常見格式其他 / 未知webp默認(rèn)選擇現(xiàn)代高壓縮格式五、支持與跳過的格式5.1 支持的輸入格式?JPEG/JPG有損壓縮適合照片PNG無(wú)損壓縮支持透明WebP現(xiàn)代格式壓縮率與畫質(zhì)均衡TIFF/TIF高質(zhì)量圖像GIF動(dòng)圖/靜態(tài)圖會(huì)被轉(zhuǎn)為靜態(tài)幀BMP位圖5.2 自動(dòng)跳過的格式??以下格式會(huì)被自動(dòng)跳過并返回失敗結(jié)果而不是嘗試壓縮ICO圖標(biāo)文件本身已針對(duì) favicon、應(yīng)用圖標(biāo)場(chǎng)景優(yōu)化直接使用原文件即可SVG矢量圖形應(yīng)保持可縮放不應(yīng)柵格化// These will return { success: false, error: Skipping ICO/SVG file... } await compressImageServer(favicon.ico, output.webp); // ? Skipped await compressImageServer(logo.svg, output.png); // ? Skipped源碼中這一判斷有兩道防線compress.ts 與 compress.ts擴(kuò)展名檢查輸入為字符串路徑時(shí)path.extname(input).toLowerCase()命中.ico/.svg直接返回錯(cuò)誤錯(cuò)誤信息形如Skipping .ICO file - format not supported for compression. Use original file instead.元數(shù)據(jù)檢查即使輸入是 Buffer無(wú)擴(kuò)展名也會(huì)調(diào)用sharpInstance.metadata()檢查真實(shí)格式若元數(shù)據(jù)為svg同樣返回Skipping SVG format - not supported for compression。這一設(shè)計(jì)保證“偽裝成其他擴(kuò)展名/以 Buffer 傳入的 SVG”也不會(huì)被誤壓縮測(cè)試用例SVG buffer input專門驗(yàn)證了這條路徑。為什么跳過ICO 已針對(duì) favicon/應(yīng)用圖標(biāo)場(chǎng)景優(yōu)化SVG 是矢量圖壓縮會(huì)破壞可縮放性。正確做法是直接使用原文件。六、返回結(jié)果結(jié)構(gòu)CompressionResulttypes.tsexport interface CompressionResult { success: boolean; originalSize?: number; // 原始大小字節(jié) compressedSize?: number; // 壓縮后大小字節(jié) compressionRatio?: number; // 壓縮率百分比(originalSize - compressedSize) / originalSize * 100 outputPath?: string; // 寫入文件時(shí)的輸出路徑 buffer?: Buffer; // 未指定 outputPath 時(shí)的壓縮結(jié)果數(shù)據(jù) error?: string; // 失敗時(shí)的錯(cuò)誤信息 }原始大小文件路徑輸入時(shí)取fs.stat的sizeBuffer 輸入時(shí)取input.lengthcompress.ts。compressionRatio為負(fù)數(shù)說明壓縮后反而更大例如 PNG 轉(zhuǎn) PNG 無(wú)損場(chǎng)景這是正常現(xiàn)象需要調(diào)用方按業(yè)務(wù)判斷。七、使用內(nèi)置壓縮預(yù)設(shè)Onlook 在packages/constants/src/files.ts中預(yù)置了 4 組常用壓縮配置可直接與compressImageServer組合使用避免每次手寫參數(shù)import { compressImageServer } from onlook/image-server; import { COMPRESSION_IMAGE_PRESETS } from onlook/constants; // Use predefined presets const result await compressImageServer(input.jpg, output.webp, COMPRESSION_IMAGE_PRESETS.web);各預(yù)設(shè)的完整定義與 README 描述一一對(duì)應(yīng)并可從源碼確認(rèn)精確值預(yù)設(shè)用途具體參數(shù)源碼值webWeb 交付優(yōu)化WebPquality 80progressiveeffort 4thumbnail小縮略圖300×300WebPquality 70keepAspectRatiohighQuality高質(zhì)量輸出JPEGquality 95progressivemozjpeglowFileSize極致壓縮體積WebPquality 60effort 6注意thumbnail預(yù)設(shè)的 300×300 配合keepAspectRatio: true意味著“等比縮放到 300×300 矩形內(nèi)”不會(huì)拉伸變形。八、錯(cuò)誤處理與健壯性設(shè)計(jì)該包采取“函數(shù)永不拋出錯(cuò)誤一律折疊進(jìn)結(jié)果對(duì)象”的策略compress.ts} catch (error) { return { success: false, error: error instanceof Error ? error.message : Unknown error occurred, }; }調(diào)用方只需檢查result.successconst result await compressImageServer(input.jpg); if (!result.success) { console.error(Compression failed:, result.error); // Common error cases: // - Skipping .ICO file - format not supported for compression. Use original file instead. // - Skipping SVG format - not supported for compression. Use original file instead. // - File not found, permission errors, corrupted files, etc. }批量接口batchCompressImagesServer同樣把整體異常收斂為單個(gè)失敗結(jié)果數(shù)組compress.ts。測(cè)試覆蓋了哪些錯(cuò)誤場(chǎng)景image.test.ts 中的Input Validation與Error Handling兩組用例驗(yàn)證了不存在的文件 →success: false空字符串輸入 →success: false損壞的圖片文件寫入非圖片內(nèi)容的假.jpg→success: false權(quán)限錯(cuò)誤輸出到/root/impossible-path.jpg→success: false偽造的.ico/.svg文件 →success: false真實(shí) ICO / SVG 文件 → 正確跳過且不產(chǎn)生輸出文件測(cè)試斷言輸出文件不存在九、Onlook 中的真實(shí)集成tRPC 壓縮接口在 Onlook 的 Web 應(yīng)用中該包被 image.ts 包裝為受保護(hù)的 tRPC mutationimage.compress是“服務(wù)端壓縮”的教科書式用法import { compressImageServer, type CompressionOptions, type CompressionResult } from onlook/image-server; import { z } from zod; import { createTRPCRouter, protectedProcedure } from ../trpc;核心流程image.ts客戶端上傳base64 編碼的圖片數(shù)據(jù)服務(wù)端Buffer.from(input.imageData, base64)還原為 Buffer以 Buffer 形式調(diào)用compressImageServer(buffer, undefined, options)不寫磁盤由于未指定outputPath返回結(jié)果中的buffer字段攜帶壓縮后數(shù)據(jù)服務(wù)端將其再轉(zhuǎn)為 base64bufferData字段傳回客戶端——因?yàn)?tRPC 結(jié)果序列化不直接支持 Buffer。同時(shí) project.ts 也在項(xiàng)目相關(guān)邏輯中導(dǎo)入了compressImageServer用于服務(wù)端圖片落盤前的壓縮。這兩個(gè)真實(shí)調(diào)用點(diǎn)可以印證Buffer 輸入 不落盤 結(jié)果折疊的組合是該包在 API 服務(wù)中的主流用法。十、測(cè)試體系與驗(yàn)證方式包內(nèi)測(cè)試位于packages/image-server/test/image.test.ts使用bun:test運(yùn)行測(cè)試樣本圖片存放在test/images/input含favicon.ico、jpg.jpg、png.png、svg.svg、webp.webp。測(cè)試維度包括輸入校驗(yàn)不存在的文件、空輸入真實(shí)圖片壓縮JPEG、PNG 壓縮到 WebP校驗(yàn)originalSize 0、compressedSize 0、compressionRatio 0且輸出文件真實(shí)存在auto 格式推導(dǎo)不指定格式時(shí)的自動(dòng)輸出縮放指定width/height后正確輸出Buffer 輸出result.buffer為合法 Buffer 且長(zhǎng)度與compressedSize一致ICO / SVG 跳過單圖與批量?jī)蓷l路徑均驗(yàn)證success: false、錯(cuò)誤信息包含Skipping .ICO file/Skipping .SVG file、且輸出文件未被創(chuàng)建Buffer 形式的 SVG 也正確跳過混合格式批量結(jié)果數(shù)量與輸入一致成功項(xiàng)有輸出文件被跳過項(xiàng)錯(cuò)誤信息正確質(zhì)量對(duì)比[95, 80, 65, 50]多檔質(zhì)量壓縮全部成功異常處理?yè)p壞文件、權(quán)限錯(cuò)誤、偽造的 ICO/SVG十一、小結(jié)與最佳實(shí)踐onlook/image-server是一個(gè)聚焦且健壯的服務(wù)端圖片壓縮模塊圍繞 Sharp 封裝了格式推導(dǎo)、格式專屬壓縮參數(shù)、縮放、批量處理與 ICO/SVG 防御性跳過并把所有錯(cuò)誤折疊進(jìn)結(jié)果對(duì)象。在實(shí)際項(xiàng)目中使用時(shí)建議遵循以下實(shí)踐嚴(yán)守服務(wù)端邊界只在 Node.js 進(jìn)程API 路由、服務(wù)端函數(shù)、腳本中導(dǎo)入勿在瀏覽器或 Electron preload 中使用優(yōu)先用預(yù)設(shè)Web 交付用COMPRESSION_IMAGE_PRESETS.web縮略圖用thumbnail追求畫質(zhì)用highQuality追求體積用lowFileSize善用 Buffer 模式處理上傳流或需要返回壓縮結(jié)果的接口時(shí)省略outputPath從result.buffer取數(shù)據(jù)始終檢查success該包不拋異常跳過與失敗都以success: falseerror表達(dá)調(diào)用方務(wù)必顯式處理ICO/SVG 直接透?jìng)魉鼈儠?huì)被跳過是設(shè)計(jì)行為請(qǐng)直接使用原文件。許可證Apache-2.0?!久赓M(fèi)下載鏈接】onlookThe Cursor for Designers ? An Open-Source AI-First Design tool ? Visually build, style, and edit your React App with AI項(xiàng)目地址: https://gitcode.com/GitHub_Trending/on/onlook創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考