国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
嗯,手搓一個(gè)TinyPng壓縮圖片的WebpackPlugin也SoEasy啦

前言

曾經(jīng)發(fā)表過(guò)一篇性能優(yōu)化的文章《前端性能優(yōu)化指南》,筆者總結(jié)了一些在項(xiàng)目開發(fā)過(guò)程中使用過(guò)的性能優(yōu)化經(jīng)驗(yàn)。說(shuō)句真話,性能優(yōu)化可能在面試過(guò)程中會(huì)有用,實(shí)際在項(xiàng)目開發(fā)過(guò)程中可能沒幾個(gè)同學(xué)會(huì)注意這些性能優(yōu)化的細(xì)節(jié)。

若經(jīng)常關(guān)注性能優(yōu)化的話題,可能會(huì)發(fā)現(xiàn)無(wú)論怎樣對(duì)代碼做最好的優(yōu)化也不及對(duì)一張圖片做一次壓縮好。所以壓縮圖片成了性能優(yōu)化里最常見的操作,不管是手動(dòng)壓縮圖片還是自動(dòng)壓縮圖片,在項(xiàng)目開發(fā)過(guò)程中必須得有。

自動(dòng)壓縮圖片通常在webpack構(gòu)建項(xiàng)目時(shí)接入一些第三方Loader&Plugin來(lái)處理。打開Github,搜素webpack image等關(guān)鍵字,Star最多還是image-webpack-loaderimagemin-webpack-plugin這兩個(gè)Loader&Plugin。很多同學(xué)可能都會(huì)選擇它們,方便快捷,簡(jiǎn)單易用,無(wú)腦接入。

可是,這兩個(gè)Loader&Plugin存在一些特別問(wèn)題,它們都是基于imagemin開發(fā)的。imagemin的某些依賴托管在國(guó)外服務(wù)器,在npm i xxx安裝它們時(shí)會(huì)默認(rèn)走GitHub Releases的托管地址,若不是規(guī)范上網(wǎng),你們是不可能安裝得上的,即使規(guī)范上網(wǎng)也不一定安裝得上。所以筆者又刨根到底發(fā)表了一篇關(guān)于NPM鏡像處理的文章《聊聊NPM鏡像那些險(xiǎn)象環(huán)生的坑》,專門解決這些因?yàn)榫W(wǎng)絡(luò)環(huán)境而導(dǎo)致安裝失敗的問(wèn)題。除了這個(gè)安裝問(wèn)題,imagemin還存在另一個(gè)大問(wèn)題,就是壓縮質(zhì)感損失得比較嚴(yán)重,圖片體積越大越明顯,壓縮出來(lái)的圖片總有幾張是失真的,而且總體壓縮率不是很高。這樣在交付項(xiàng)目時(shí)有可能被細(xì)心的QA小姐姐抓個(gè)正著,怎么和設(shè)計(jì)圖對(duì)比起來(lái)不清晰?。?/p>

工具

圖片壓縮工具

此時(shí)可能有些同學(xué)已轉(zhuǎn)戰(zhàn)到手動(dòng)壓縮圖片了。比較好用的圖片壓縮工具無(wú)非就是以下幾個(gè),若有更好用的工具麻煩在評(píng)論里補(bǔ)充喔!同時(shí)筆者也整理出它們的區(qū)別,供各位同學(xué)參考。

工具開源收費(fèi)API免費(fèi)體驗(yàn)
QuickPicture??????可壓縮類型較多,壓縮質(zhì)感較好,有體積限制,有數(shù)量限制
ShrinkMe??????可壓縮類型較多,壓縮質(zhì)感一般,無(wú)數(shù)量限制,有體積限制
Squoosh??????可壓縮類型較少,壓縮質(zhì)感一般,無(wú)數(shù)量限制,有體積限制
TinyJpg??????可壓縮類型較少,壓縮質(zhì)感很好,有數(shù)量限制,有體積限制
TinyPng??????可壓縮類型較少,壓縮質(zhì)感很好,有數(shù)量限制,有體積限制
Zhitu??????可壓縮類型一般,壓縮質(zhì)感一般,有數(shù)量限制,有體積限制

從上述表格對(duì)比可看出,免費(fèi)體驗(yàn)都會(huì)存在體積限制,這個(gè)可理解,即使收費(fèi)也一樣,畢竟每個(gè)人都上傳單張10多M的圖片,哪個(gè)服務(wù)器能受得了。再來(lái)就是數(shù)量限制,一次只能上傳20張,好像有個(gè)規(guī)律,壓縮質(zhì)感好就限制數(shù)量,否則就不限制數(shù)量,當(dāng)然收費(fèi)后就沒有限制了。再來(lái)就是可壓縮類型,圖片類型一般是jpgpng、gif、svgwebp,gif壓縮后一般都會(huì)失真,svg通常用在矢量圖標(biāo)上很少用在場(chǎng)景圖片上,webp由于兼容性問(wèn)題很少被使用,故能壓縮jpgpng就足夠了。當(dāng)然壓縮質(zhì)感是最優(yōu)考慮,綜上所述,大部分同學(xué)都會(huì)選擇TinyJpgTinyPng,其實(shí)它倆就是兄弟,出自同一廠商。

在筆者公眾號(hào)的微信討論群里發(fā)起了一個(gè)簡(jiǎn)單的投票,最終還是TinyJpgTinyPng勝出。

TinyJpg/TinyPng存在問(wèn)題
  • 上傳下載全靠手動(dòng)
  • 只能壓縮jpgpng
  • 每次只能壓縮20
  • 每張?bào)w積最大不能超過(guò)5M
  • 可視化處理信息不是特別齊全
TinyJpg/TinyPng壓縮原理

TinyJpg/TinyPng使用智能有損壓縮技術(shù)將圖片體積降低,選擇性地減少圖片中相似顏色,只需很少字節(jié)就能保存數(shù)據(jù)。對(duì)視覺影響幾乎不可見,但是在文件體積上就有很大的差別。而使用到智能有損壓縮技術(shù)被稱為量化

TinyJpg/TinyPng在壓縮png文件時(shí)效果更顯著。掃描圖片中相似顏色并將其合并,通過(guò)減少顏色數(shù)量將24位png文件轉(zhuǎn)換成體積更小的8位png文件,丟棄所有不必要的元數(shù)據(jù)。

大部分png文件都有50%~70%的壓縮率,即使視力再好也很難區(qū)分出來(lái)。使用優(yōu)化過(guò)的圖片可減少帶寬流量和加載時(shí)間,整個(gè)網(wǎng)站使用到的圖片經(jīng)TinyJpg/TinyPng壓縮一遍,其成效是再多的代碼優(yōu)化也無(wú)法追趕得上的。

TinyJpg/TinyPng開發(fā)API

查閱相關(guān)資料,發(fā)現(xiàn)TinyJpg/TinyPng暫時(shí)還未開源其壓縮算法,不過(guò)提供了適合開發(fā)者使用的API。有興趣的同學(xué)可到其開發(fā)API文檔瞧瞧。

Node方面,TinyJpg/TinyPng官方提供了tinify作為壓縮圖片的核心JS庫(kù),使用很簡(jiǎn)單,看文檔吧??墒菗Q成開發(fā)API還是逃不過(guò)收費(fèi),你是想包月呢還是免費(fèi)呢,想免費(fèi)的話就繼續(xù)往下看,土豪隨意!

實(shí)現(xiàn)

筆者也是經(jīng)常使用TinyJpg/TinyPng的程序猿,收費(fèi),那是不可能的??。尋找突破口,解決問(wèn)題,是作為一位程序猿最基本的素養(yǎng)。我們需明確什么問(wèn)題,需解決什么問(wèn)題。

分析

從上述得知,只需對(duì)TinyJpg/TinyPng原有功能改造成以下功能。

  • 上傳下載全自動(dòng)
  • 可壓縮jpgpng
  • 沒有數(shù)量限制
  • 存在體積限制,最大體積不能超過(guò)5M
  • 壓縮成功與否輸出詳細(xì)信息
自動(dòng)處理

對(duì)于前端開發(fā)者來(lái)說(shuō),這種無(wú)腦的上傳下載操作必須得自動(dòng)化,省事省心省力。但是這個(gè)操作得結(jié)合webpack來(lái)處理,到底是開發(fā)成Loader還是Plugin,后面再分析。不過(guò)細(xì)心的同學(xué)看標(biāo)題就知道用什么方式處理了。

壓縮類型

gif壓縮后一般都會(huì)失真,svg通常用在矢量圖標(biāo)上很少用在場(chǎng)景圖片上,webp由于兼容性問(wèn)題很少被使用,故能壓縮jpgpng就足夠了。在過(guò)濾圖片時(shí),使用path模塊判斷文件類型是否為jpgpng,是則繼續(xù)處理,否則不處理。

數(shù)量限制

數(shù)量限制當(dāng)然是不能存在的,萬(wàn)一項(xiàng)目里超過(guò)20張圖片,那不是得分批處理,這個(gè)不能有。對(duì)于這種無(wú)需登錄狀態(tài)就能處理一些用戶文件的網(wǎng)站,通常都會(huì)通過(guò)IP來(lái)限制用戶的操作次數(shù)。有些同學(xué)可能會(huì)說(shuō),刷新頁(yè)面不就行了嗎,每次壓縮20張圖片,再刷新再壓縮,萬(wàn)一有500張圖片呢,你就刷新25次嗎,這樣很好玩是吧!

由于大多數(shù)Web架構(gòu)很少會(huì)將應(yīng)用服務(wù)器直接對(duì)外提供服務(wù),一般都會(huì)設(shè)置一層Nginx作為代理和負(fù)載均衡,有的甚至可能有多層代理。鑒于大多數(shù)Web架構(gòu)都是使用Nginx作為反向代理,用戶請(qǐng)求不是直接請(qǐng)求應(yīng)用服務(wù)器的,而是通過(guò)Nginx設(shè)置的統(tǒng)一接入層將用戶請(qǐng)求轉(zhuǎn)發(fā)到服務(wù)器的,所以可通過(guò)設(shè)置HTTP請(qǐng)求頭字段X-Forwarded-For來(lái)偽造IP。

X-Forwarded-For指用來(lái)識(shí)別通過(guò)代理負(fù)載均衡的方式連接到Web服務(wù)器的客戶端最原始的IP地址的HTTP請(qǐng)求頭字段。當(dāng)然,這個(gè)IP也不是一成不變的,每次請(qǐng)求都需隨機(jī)更換IP,騙過(guò)應(yīng)用服務(wù)器。若應(yīng)用服務(wù)器增加了偽造IP識(shí)別,那可能就無(wú)法繼續(xù)使用隨機(jī)IP了。

體積限制

體積限制這個(gè)能理解,也沒必要搞一張那么大的圖片,多浪費(fèi)帶寬流量和加載時(shí)間啊。在上傳圖片時(shí),使用fs模塊判斷文件體積是否超過(guò)5M,是則不上傳,否則繼續(xù)上傳。當(dāng)然,交給TinyJpg/TinyPng接口判斷也行。

輸出信息

壓縮成功與否得讓別人知道,輸出原始大小、壓縮大小、壓縮率和錯(cuò)誤提示等,讓別人清楚這些處理信息。

編碼

通過(guò)上述抽絲剝繭的分析,那么就開始著手編碼了。

隨機(jī)生成HTTP請(qǐng)求頭

既然可通過(guò)X-Forwarded-For來(lái)偽造IP,那么得有一個(gè)隨機(jī)生成HTTP請(qǐng)求頭字段的函數(shù),每次請(qǐng)求接口時(shí)都隨機(jī)生成相關(guān)的請(qǐng)求頭字段。打開tinyjpg.comtinypng.com上傳一張圖片,通過(guò)Chrome DevTools分析Network發(fā)現(xiàn)其請(qǐng)求接口是web/shrink。另外每次請(qǐng)求也不要集中在單一的hostname上,隨機(jī)派發(fā)到tinyjpg.comtinypng.com上會(huì)更好。通過(guò)封裝RandomHeader函數(shù)隨機(jī)生成請(qǐng)求頭信息,后續(xù)使用https模塊RandomHeader()生成的配置作為入?yún)⑦M(jìn)行請(qǐng)求。

trample是筆者開發(fā)的一個(gè)Web/Node通用函數(shù)工具庫(kù),包含常規(guī)的工具函數(shù),助你少寫更多通用代碼。詳情請(qǐng)查看文檔,順便給一個(gè)Star以作鼓勵(lì)。

const { RandomNum } = require('trample/node');const TINYIMG_URL = [ 'tinyjpg.com', 'tinypng.com'];function RandomHeader() { const ip = new Array(4).fill(0).map(() => parseInt(Math.random() * 255)).join('.'); const index = RandomNum(0, 1); return { headers: { 'Cache-Control': 'no-cache', 'Content-Type': 'application/x-www-form-urlencoded', 'Postman-Token': Date.now(), 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36', 'X-Forwarded-For': ip }, hostname: TINYIMG_URL[index], method: 'POST', path: '/web/shrink', rejectUnauthorized: false };}
上傳圖片與下載圖片

使用Promise封裝上傳圖片下載圖片的函數(shù),方便后續(xù)使用Async/Await同步化異步代碼。以下函數(shù)的具體斷點(diǎn)調(diào)試就不說(shuō)了,有興趣的同學(xué)自行調(diào)試函數(shù)的入?yún)⒑统鰠⒐?/p>

const Https = require('https');const Url = require('url');function UploadImg(file) {    const opts = RandomHeader();    return new Promise((resolve, reject) => {        const req = Https.request(opts, res => res.on('data', data => {            const obj = JSON.parse(data.toString());            obj.error ? reject(obj.message) : resolve(obj);        }));        req.write(file, 'binary');        req.on('error', e => reject(e));        req.end();    });}function DownloadImg(url) {    const opts = new Url.URL(url);    return new Promise((resolve, reject) => {        const req = Https.request(opts, res => {            let file = '';            res.setEncoding('binary');            res.on('data', chunk => file += chunk);            res.on('end', () => resolve(file));        });        req.on('error', e => reject(e));        req.end();    });}
壓縮圖片

通過(guò)上傳圖片函數(shù)獲取壓縮后的圖片信息,再依據(jù)圖片信息通過(guò)下載圖片函數(shù)生成本地文件。

const Fs = require('fs');const Path = require('path');const Chalk = require('chalk');const Figures = require('figures');const { ByteSize, RoundNum } = require('trample/node');async function CompressImg(path) { try { const file = Fs.readFileSync(path, 'binary'); const obj = await UploadImg(file); const data = await DownloadImg(obj.output.url); const oldSize = Chalk.redBright(ByteSize(obj.input.size)); const newSize = Chalk.greenBright(ByteSize(obj.output.size)); const ratio = Chalk.blueBright(RoundNum(1 - obj.output.ratio, 2, true)); const dpath = Path.join('img', Path.basename(path)); const msg = `${Figures.tick} Compressed [${Chalk.yellowBright(path)}] completed: Old Size ${oldSize}, New Size ${newSize}, Optimization Ratio ${ratio}`; Fs.writeFileSync(dpath, data, 'binary'); return Promise.resolve(msg); } catch (err) { const msg = `${Figures.cross} Compressed [${Chalk.yellowBright(path)}] failed: ${Chalk.redBright(err)}`; return Promise.resolve(msg); }}
壓縮目標(biāo)圖片

完成上述步驟對(duì)應(yīng)的函數(shù)后,就能自由壓縮圖片了,以下使用一張圖片作為演示。

const Ora = require('ora');(async() => {    const spinner = Ora('Image is compressing......').start();    const res = await CompressImg('src/pig.png');    spinner.stop();    console.log(res);})();

你看,壓縮完后笨豬都變帥豬了,能電眼的豬都是好豬。源碼請(qǐng)查看compress-img。

若壓縮指定文件夾里符合條件的所有圖片,可通過(guò)fs模塊獲取圖片并使用map()將單個(gè)圖片路徑映射為CompressImg(path),再通過(guò)Promise.all()操作即可。在這里就不貼代碼了,當(dāng)作思考題,自行完成。

將上述壓縮圖片的功能封裝成Loader還是Plugin呢?接下來(lái)會(huì)一步一步分析。

Loader&Plugin

webpack是一個(gè)前端資源打包工具,它根據(jù)模塊依賴關(guān)系進(jìn)行靜態(tài)分析,然后將這些模塊按照指定規(guī)則生成對(duì)應(yīng)的靜態(tài)資源。

網(wǎng)上一大堆webpack教程,筆者就不再花大篇幅啰嗦了,相信各位同學(xué)都是一位標(biāo)準(zhǔn)的Webpack配置工程師。以下簡(jiǎn)單回顧一次webpack的組成、構(gòu)建機(jī)制和構(gòu)建流程,相信也能從這些知識(shí)點(diǎn)中定位出LoaderPluginWebpack構(gòu)建流程中是處于一個(gè)什么樣的角色地位。

本文所說(shuō)的webpack都是基于webpack v4
組成
  • Entry:入口
  • Output:輸出
  • Loader:轉(zhuǎn)換器
  • Plugin:擴(kuò)展器
  • Mode:模式
  • Module:模塊
  • Target:目標(biāo)
構(gòu)建機(jī)制
  • 通過(guò)Babel轉(zhuǎn)換代碼并生成單個(gè)文件依賴
  • 從入口文件開始遞歸分析并生成依賴圖譜
  • 將各個(gè)引用模塊打包成一個(gè)立即執(zhí)行函數(shù)
  • 將最終bundle文件寫入bundle.js
構(gòu)建流程
  • 初始

    • 初始參數(shù):合并命令行和配置文件的參數(shù)
  • 編譯

    • 執(zhí)行編譯:依據(jù)參數(shù)初始Compiler對(duì)象,加載所有Plugin,執(zhí)行run()
    • 確定入口:依據(jù)配置文件找出所有入口文件
    • 編譯模塊:依據(jù)入口文件找出所有依賴模塊關(guān)系,調(diào)用所有Loader進(jìn)行轉(zhuǎn)換
    • 生成圖譜:得到每個(gè)模塊轉(zhuǎn)換后的內(nèi)容及其之間的依賴關(guān)系
  • 輸出

    • 輸出資源:依據(jù)依賴關(guān)系將模塊組裝成塊再組裝成包(module → chunk → bundle)
    • 生成文件:依據(jù)配置文件將確認(rèn)輸出的內(nèi)容寫入文件系統(tǒng)
Loader

Loader用于轉(zhuǎn)換模塊源碼,筆者將其翻譯為轉(zhuǎn)換器Loader可將所有類型文件轉(zhuǎn)換為webpack能夠處理的有效模塊,然后利用webpack的打包能力對(duì)它們進(jìn)行二次處理。

Loader具有以下特點(diǎn):

  • 單一職責(zé)原則(只完成一種轉(zhuǎn)換)
  • 轉(zhuǎn)換接收內(nèi)容
  • 返回轉(zhuǎn)換結(jié)果
  • 支持鏈?zhǔn)秸{(diào)用

Loader將所有類型文件轉(zhuǎn)換為應(yīng)用程序的依賴圖譜可直接引用的模塊,所以Loader可用于編譯一些文件,例如pug → html、sass → css、less → csses5 → es6、ts → js等。

處理一個(gè)文件可使用多個(gè)Loader,Loader的執(zhí)行順序和配置順序是相反的,即末尾Loader最先執(zhí)行,開頭Loader最后執(zhí)行。最先執(zhí)行的Loader接收源文件內(nèi)容作為參數(shù),其它Loader接收前一個(gè)執(zhí)行的Loader的返回值作為參數(shù),最后執(zhí)行的Loader會(huì)返回該文件的轉(zhuǎn)換結(jié)果。一句話概括:富土康流水線廠工。

Loader開發(fā)思路總結(jié)如下:

  • 通過(guò)module.exports導(dǎo)出一個(gè)函數(shù)
  • 函數(shù)第一默認(rèn)參數(shù)為source(源文件內(nèi)容)
  • 在函數(shù)體中處理資源(可引入第三方模塊擴(kuò)展功能)
  • 通過(guò)return返回最終轉(zhuǎn)換結(jié)果(字符串形式)
編寫Loader時(shí)要遵循單一職責(zé)原則,每個(gè)Loader只做一種轉(zhuǎn)換工作
Plugin

Plugin用于擴(kuò)展執(zhí)行范圍更廣的任務(wù),筆者將其翻譯為擴(kuò)展器Plugin的范圍很廣,在Webpack構(gòu)建流程里從開始到結(jié)束都能找到時(shí)機(jī)作為插入點(diǎn),只要你想不到?jīng)]有你做不到。所以筆者認(rèn)為Plugin的功能比Loader更加強(qiáng)大。

Plugin具有以下特點(diǎn):

  • 監(jiān)聽webpack運(yùn)行生命周期中廣播的事件
  • 在合適時(shí)機(jī)通過(guò)webpack提供的API改變輸出結(jié)果
  • webpack的Tapable事件流機(jī)制保證Plugin的有序性

webpack運(yùn)行生命周期中會(huì)廣播出許多事件,Plugin可監(jiān)聽這些事件并在合適時(shí)機(jī)通過(guò)webpack提供的API改變輸出結(jié)果。在webpack啟動(dòng)后,在讀取配置過(guò)程中執(zhí)行new MyPlugin(opts)初始化自定義Plugin獲取其實(shí)例,在初始化Compiler對(duì)象后,通過(guò)compiler.hooks.event.tap(PLUGIN_NAME, callback)監(jiān)聽webpack廣播事件,當(dāng)捕抓到指定事件后,會(huì)通過(guò)Compilation對(duì)象操作相關(guān)業(yè)務(wù)邏輯。一句話概括:自己看著辦

Plugin開發(fā)思路總結(jié)如下:

  • 通過(guò)module.exports導(dǎo)出一個(gè)函數(shù)或類
  • 函數(shù)原型或類上綁定apply()訪問(wèn)Compiler對(duì)象
  • apply()中指定一個(gè)綁定到webpack自身的事件鉤子
  • 在事件鉤子中通過(guò)webpack提供的API處理資源(可引入第三方模塊擴(kuò)展功能)
  • 通過(guò)webpack提供的方法返回該資源
傳給每個(gè)Plugin的Compiler和Compilation都是同一個(gè)引用,若修改它們身上的屬性會(huì)影響后面的Plugin,所以需謹(jǐn)慎操作
Loader/Plugin區(qū)別
  • 本質(zhì)

    • Loader本質(zhì)是一個(gè)函數(shù),轉(zhuǎn)換接收內(nèi)容,返回轉(zhuǎn)換結(jié)果
    • Plugin本質(zhì)是一個(gè)類,監(jiān)聽webpack運(yùn)行生命周期中廣播的事件,在合適時(shí)機(jī)通過(guò)webpack提供的API改變輸出結(jié)果
  • 配置

    • Loadermodule.rule中配置,類型是數(shù)組,每一項(xiàng)對(duì)應(yīng)一個(gè)模塊解析規(guī)則
    • Pluginplugin中配置,類型是數(shù)組,每一項(xiàng)對(duì)應(yīng)一個(gè)擴(kuò)展器實(shí)例,參數(shù)通過(guò)構(gòu)造函數(shù)傳入

封裝

分析

從上述可知LoaderPlugin在角色定位和執(zhí)行機(jī)制上有很多不一樣,到底如何選擇呢?各有各好,當(dāng)然還是需分析后進(jìn)行選擇。

Loaderwebpack中扮演著轉(zhuǎn)換器的角色,用于轉(zhuǎn)換模塊源碼,簡(jiǎn)單理解就是將文件轉(zhuǎn)換成另外形式的文件,而本文主題是壓縮圖片,jpg壓縮后還是jpg,png壓縮后還是png,在文件類型上來(lái)說(shuō)還是沒有變化。Loader的轉(zhuǎn)換過(guò)程是附屬在整個(gè)Webpack構(gòu)建流程中的,意味著打包時(shí)間包含了壓縮圖片的時(shí)間成本,對(duì)于追求webpack性能優(yōu)化來(lái)說(shuō)實(shí)屬有點(diǎn)違背原則。而Plugin恰好是監(jiān)聽webpack運(yùn)行生命周期中廣播的事件,在合適時(shí)機(jī)通過(guò)webpack提供的API改變輸出結(jié)果,所以可在整個(gè)Webpack構(gòu)建流程完成后(全部打包文件輸出完成后)插入壓縮圖片的操作。換句話說(shuō),打包時(shí)間不再包含壓縮圖片的時(shí)間成本,打包完成后該干嘛就干嘛,還能干嘛,壓縮圖片啊。

所以依據(jù)需求情況,Plugin作為首選。

編碼

依據(jù)上述Plugin開發(fā)思路,那么就開始著手編碼了。

筆者把這個(gè)壓縮圖片的Plugin命名為tinyimg-webpack-plugin,tinyimg意味著TinyJpgTinyPng合體。

新建項(xiàng)目,目錄結(jié)構(gòu)如下。

tinyimg-webpack-plugin├─ src│  ├─ index.js│  ├─ schema.json├─ util│  ├─ getting.js│  ├─ setting.js├─ .gitignore├─ .npmignore├─ license├─ package.json├─ readme.md

主要文件如下。

  • src

    • index.js:入口函數(shù)
    • schema.json:參數(shù)校驗(yàn)
  • util

    • getting.js:常量集合
    • setting.js:函數(shù)集合

安裝項(xiàng)目所需模塊,和上述compress-img的依賴一致,額外安裝schema-utils用于校驗(yàn)Plugin參數(shù)是否符合規(guī)定。

npm i chalk figures ora schema-utils trample
封裝常量集合和函數(shù)集合

將上述compress-imgTINYIMG_URLRandomHeader()封裝到工具集合中,其中常量集合增加IMG_REGEXPPLUGIN_NAME兩個(gè)常量。

// getting.jsconst IMG_REGEXP = /\.(jpe?g|png)$/;const PLUGIN_NAME = 'tinyimg-webpack-plugin';const TINYIMG_URL = [    'tinyjpg.com',    'tinypng.com'];module.exports = {    IMG_REGEXP,    PLUGIN_NAME,    TINYIMG_URL};
// setting.jsconst { RandomNum } = require('trample/node');const { TINYIMG_URL } = require('./getting');function RandomHeader() { const ip = new Array(4).fill(0).map(() => parseInt(Math.random() * 255)).join('.'); const index = RandomNum(0, 1); return { headers: { 'Cache-Control': 'no-cache', 'Content-Type': 'application/x-www-form-urlencoded', 'Postman-Token': Date.now(), 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36', 'X-Forwarded-For': ip }, hostname: TINYIMG_URL[index], method: 'POST', path: '/web/shrink', rejectUnauthorized: false };}module.exports = { RandomHeader};
通過(guò)module.exports導(dǎo)出一個(gè)函數(shù)或類
// index.jsmodule.exports = class TinyimgWebpackPlugin {};
函數(shù)原型或類上綁定apply()訪問(wèn)Compiler對(duì)象
// index.jsmodule.exports = class TinyimgWebpackPlugin { apply(compiler) { // Do Something }};
apply()中指定一個(gè)綁定到webpack自身的事件鉤子

從上述分析中可知,在全部打包文件輸出完成后插入壓縮圖片的操作,所以應(yīng)該選擇該時(shí)機(jī)對(duì)應(yīng)的事件鉤子。從Webpack Compiler Hooks API文檔中可發(fā)現(xiàn),emit正是這個(gè)Plugin所需的事件鉤子。emit生成資源到輸出目錄前執(zhí)行,此刻可獲取所有圖片文件的數(shù)據(jù)和輸出路徑。

為了方便在特定條件下啟用功能打印日志,所以設(shè)置相關(guān)配置。

  • enabled:是否啟用功能
  • logged:是否打印日志

apply()中處理相關(guān)業(yè)務(wù)邏輯,可能使用到Plugin的入?yún)?,那么就得?duì)參數(shù)進(jìn)行校驗(yàn)。定義一個(gè)PluginSchema,通過(guò)schema-utils來(lái)校驗(yàn)Plugin的入?yún)ⅰ?/p>

// schema.json{    'type': 'object',    'properties': {        'enabled': {            'description': 'start plugin',            'type': 'boolean'        },        'logged': {            'description': 'print log',            'type': 'boolean'        }    },    'additionalProperties': false}
// index.jsconst SchemaUtils = require('schema-utils');const { PLUGIN_NAME } = require('../util/getting');const Schema = require('./schema');module.exports = class TinyimgWebpackPlugin { constructor(opts) { this.opts = opts; } apply(compiler) { const { enabled } = this.opts; SchemaUtils(Schema, this.opts, { name: PLUGIN_NAME }); enabled && compiler.hooks.emit.tap(PLUGIN_NAME, compilation => { // Do Something }); }};
整合compress-imgPlugin

在整合過(guò)程中會(huì)有一些小修改,各位同學(xué)可對(duì)比看看哪些細(xì)節(jié)發(fā)生了變化。

// index.jsconst Fs = require('fs');const Https = require('https');const Url = require('url');const Chalk = require('chalk');const Figures = require('figures');const { ByteSize, RoundNum } = require('trample/node');const { RandomHeader } = require('../util/setting');module.exports = class TinyimgWebpackPlugin {    constructor(opts) { ... }    apply(compiler) { ... }    async compressImg(assets, path) {        try {            const file = assets[path].source();            const obj = await this.uploadImg(file);            const data = await this.downloadImg(obj.output.url);            const oldSize = Chalk.redBright(ByteSize(obj.input.size));            const newSize = Chalk.greenBright(ByteSize(obj.output.size));            const ratio = Chalk.blueBright(RoundNum(1 - obj.output.ratio, 2, true));            const dpath = assets[path].existsAt;            const msg = `${Figures.tick} Compressed [${Chalk.yellowBright(path)}] completed: Old Size ${oldSize}, New Size ${newSize}, Optimization Ratio ${ratio}`;            Fs.writeFileSync(dpath, data, 'binary');            return Promise.resolve(msg);        } catch (err) {            const msg = `${Figures.cross} Compressed [${Chalk.yellowBright(path)}] failed: ${Chalk.redBright(err)}`;            return Promise.resolve(msg);        }    }    downloadImg(url) {        const opts = new Url.URL(url);        return new Promise((resolve, reject) => {            const req = Https.request(opts, res => {                let file = '';                res.setEncoding('binary');                res.on('data', chunk => file += chunk);                res.on('end', () => resolve(file));            });            req.on('error', e => reject(e));            req.end();        });    }    uploadImg(file) {        const opts = RandomHeader();        return new Promise((resolve, reject) => {            const req = Https.request(opts, res => res.on('data', data => {                const obj = JSON.parse(data.toString());                obj.error ? reject(obj.message) : resolve(obj);            }));            req.write(file, 'binary');            req.on('error', e => reject(e));            req.end();        });    }};
在事件鉤子中通過(guò)webpack提供的API處理資源

通過(guò)compilation.assets獲取全部打包文件的對(duì)象,篩選出jpgpng,使用map()將單個(gè)圖片數(shù)據(jù)映射為this.compressImg(file),再通過(guò)Promise.all()操作即可。

整個(gè)業(yè)務(wù)邏輯結(jié)合了PromiseAsync/Await兩個(gè)ES6常用特性,它倆組合起來(lái)玩異步編程極其有趣,關(guān)于它倆更多細(xì)節(jié)可查看筆者這篇4000點(diǎn)贊量14萬(wàn)閱讀量的文章《1.5萬(wàn)字概括ES6全部特性》。

// index.jsconst Ora = require('ora');const SchemaUtils = require('schema-utils');const { IMG_REGEXP, PLUGIN_NAME } = require('../util/getting');const Schema = require('./schema');module.exports = class TinyimgWebpackPlugin { constructor(opts) { ... } apply(compiler) { const { enabled, logged } = this.opts; SchemaUtils(Schema, this.opts, { name: PLUGIN_NAME }); enabled && compiler.hooks.emit.tap(PLUGIN_NAME, compilation => { const imgs = Object.keys(compilation.assets).filter(v => IMG_REGEXP.test(v)); if (!imgs.length) return Promise.resolve(); const promises = imgs.map(v => this.compressImg(compilation.assets, v)); const spinner = Ora('Image is compressing......').start(); return Promise.all(promises).then(res => { spinner.stop(); logged && res.forEach(v => console.log(v)); }); }); } async compressImg(assets, path) { ... } downloadImg(url) { ... } uploadImg(file) { ... }};
通過(guò)webpack提供的方法返回該資源

由于壓縮圖片的操作是在整個(gè)Webpack構(gòu)建流程完成后,所以沒有什么可返回了,故不作處理。

控制webpack依賴版本

由于tinyimg-webpack-plugin基于webpack v4,所以需在package.json中添加peerDependencies,用來(lái)告知安裝該Plugin的模塊必須存在peerDependencies里的依賴。

{    'peerDependencies': {        'webpack': '>= 4.0.0',        'webpack-cli': '>= 3.0.0'    }}
總結(jié)

按照上述總結(jié)的開發(fā)思路一步一步來(lái)完成編碼,其實(shí)是挺簡(jiǎn)單的。若需開發(fā)一些跟自己項(xiàng)目相關(guān)的Plugin,還是需多多熟悉Webpack Compiler Hooks API文檔,相信各位同學(xué)都能手戳一個(gè)完美的Plugin出來(lái)。

tinyimg-webpack-plugin源碼請(qǐng)戳這里查看,Star一個(gè)如何,嘻嘻。

測(cè)試

整個(gè)Plugin開發(fā)完成,接下來(lái)需走一遍測(cè)試流程,看能不能把這個(gè)壓縮圖片的擴(kuò)展器跑通。相信各位同學(xué)都是一位標(biāo)準(zhǔn)的Webpack配置工程師,可自行編寫測(cè)試Demo驗(yàn)證你們的Plugin。

在根目錄下創(chuàng)建test文件夾,并按照以下目錄結(jié)構(gòu)加入文件。

tinyimg-webpack-plugin├─ test│ ├─ src│ │ ├─ img│ │ │ ├─ favicon.ico│ │ │ ├─ gz.jpg│ │ │ ├─ pig-1.jpg│ │ │ ├─ pig-2.jpg│ │ │ ├─ pig-3.jpg│ │ ├─ index.html│ │ ├─ index.js│ │ ├─ index.scss│ │ ├─ reset.css│ └─ webpack.config.js

安裝測(cè)試Demo所需的webpack相關(guān)配置模塊。

npm i -D @babel/core @babel/preset-env babel-loader clean-webpack-plugin css-loader file-loader html-webpack-plugin mini-css-extract-plugin node-sass sass sass-loader style-loader url-loader webpack webpack-cli webpackbar

安裝完成后,著手完善webpack.config.js代碼,代碼量有點(diǎn)多,直接貼鏈接好了,請(qǐng)戳這里。

最后在package.json中的scripts插入以下npm scripts,然后執(zhí)行npm run test調(diào)試測(cè)試Demo。

{ 'scripts': { 'test': 'webpack --config test/webpack.config.js' }}

發(fā)布

發(fā)布到NPM倉(cāng)庫(kù)上非常簡(jiǎn)單,僅需幾行命令。若還沒注冊(cè),趕緊去NPM上注冊(cè)一個(gè)賬號(hào)。若當(dāng)前鏡像為淘寶鏡像,需執(zhí)行npm config set registry https://registry.npmjs.org/切換回源鏡像。

接下來(lái)一波操作就可完成發(fā)布了。

  • 進(jìn)入目錄:cd my-plugin
  • 登錄賬號(hào):npm login
  • 校驗(yàn)狀態(tài):npm whoami
  • 發(fā)布模塊:npm publish
  • 退出賬號(hào):npm logout

若不想牢記這么多命令,可用筆者開發(fā)的pkg-master一鍵發(fā)布,若存在某些錯(cuò)誤會(huì)立馬中斷發(fā)布并提示錯(cuò)誤信息,是一個(gè)非常好用的集成創(chuàng)建和發(fā)布的NPM模塊管理工具。詳情請(qǐng)查看文檔,順便給一個(gè)Star以作鼓勵(lì)。

安裝

npm i -g pkg-master

使用
命令縮寫功能描述
pkg-master createpkg-master c創(chuàng)建模塊生成模塊的基礎(chǔ)文件
pkg-master publishpkg-master p發(fā)布模塊檢測(cè)NPM的運(yùn)行環(huán)境賬號(hào)狀態(tài),通過(guò)則自動(dòng)發(fā)布模塊

接入

安裝

npm i tinyimg-webpack-plugin

使用
配置功能格式描述
enabled是否啟用功能true/false建議只在生產(chǎn)環(huán)境下開啟
logged是否打印日志true/false打印處理信息

webpack.config.jswebpack配置插入以下代碼。

在CommonJS中使用
const TinyimgPlugin = require('tinyimg-webpack-plugin');module.exports = {    plugins: [        new TinyimgPlugin({            enabled: process.env.NODE_ENV === 'production',            logged: true        })    ]};
在ESM中使用

必須在babel加持下的Node環(huán)境中使用

import TinyimgPlugin from 'tinyimg-webpack-plugin';export default { plugins: [ new TinyimgPlugin({ enabled: process.env.NODE_ENV === 'production', logged: true }) ]};
推薦一個(gè)零配置開箱即用的React/Vue應(yīng)用自動(dòng)化構(gòu)建腳手架

bruce-cli是一個(gè)React/Vue應(yīng)用自動(dòng)化構(gòu)建腳手架,其零配置開箱即用的優(yōu)點(diǎn)非常適合入門級(jí)、初中級(jí)、快速開發(fā)項(xiàng)目的前端同學(xué)使用,還可通過(guò)創(chuàng)建brucerc.js文件來(lái)覆蓋其默認(rèn)配置,只需專注業(yè)務(wù)代碼的編寫無(wú)需關(guān)注構(gòu)建代碼的編寫,讓項(xiàng)目結(jié)構(gòu)更簡(jiǎn)潔。使用時(shí)記得查看文檔喲,喜歡的話給個(gè)Star。

當(dāng)然,筆者已將tinyimg-webpack-plugin集成到bruce-cli中,零配置開箱即用走起。

  • Github地址:請(qǐng)戳這里
  • 官網(wǎng)文檔:請(qǐng)戳這里
  • 掘金文檔:請(qǐng)戳這里

總結(jié)

總體來(lái)說(shuō)開發(fā)一個(gè)Webpack Plugin不難,只需好好分析需求,了解webpack運(yùn)行生命周期中廣播的事件,編寫自定義Plugin在合適時(shí)機(jī)通過(guò)webpack提供的API改變輸出結(jié)果。

若覺得tinyimg-webpack-plugin對(duì)你有幫助,可在Issue提出你的寶貴建議,筆者會(huì)認(rèn)真閱讀并整合你的建議。喜歡tinyimg-webpack-plugin的請(qǐng)給一個(gè)Star,或Fork本項(xiàng)目到自己的Github上,根據(jù)自身需求定制功能。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
webpack之tree shaking
你真的理解 Webpack?請(qǐng)回答下列問(wèn)題
webpack的入門實(shí)踐,看這篇就夠了
webpack教程八:構(gòu)建簡(jiǎn)單web服務(wù)器
你必須要知道的babel二三事
TinyJPG 線上 JPG、PNG 圖片壓縮工具,輕鬆減少圖片 70% 大小
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服