欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

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

前言

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

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

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

工具

圖片壓縮工具

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

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

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

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

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

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

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

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

TinyJpg/TinyPng開(kāi)發(fā)API

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

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

實(shí)現

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

分析

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

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

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

壓縮類(lèi)型

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

數量限制

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

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

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

體積限制

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

輸出信息

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

編碼

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

隨機生成HTTP請求頭

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

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

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封裝上傳圖片下載圖片的函數,方便后續使用Async/Await同步化異步代碼。以下函數的具體斷點(diǎn)調試就不說(shuō)了,有興趣的同學(xué)自行調試函數的入參和出參哈!

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ò)上傳圖片函數獲取壓縮后的圖片信息,再依據圖片信息通過(guò)下載圖片函數生成本地文件。

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); }}
壓縮目標圖片

完成上述步驟對應的函數后,就能自由壓縮圖片了,以下使用一張圖片作為演示。

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

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

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

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

Loader&Plugin

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

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

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

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

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

    • 輸出資源:依據依賴(lài)關(guān)系將模塊組裝成塊再組裝成包(module → chunk → bundle)
    • 生成文件:依據配置文件將確認輸出的內容寫(xiě)入文件系統
Loader

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

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

  • 單一職責原則(只完成一種轉換)
  • 轉換接收內容
  • 返回轉換結果
  • 支持鏈式調用

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

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

Loader開(kāi)發(fā)思路總結如下:

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

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

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

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

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

Plugin開(kāi)發(fā)思路總結如下:

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

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

    • Loadermodule.rule中配置,類(lèi)型是數組,每一項對應一個(gè)模塊解析規則
    • Pluginplugin中配置,類(lèi)型是數組,每一項對應一個(gè)擴展器實(shí)例,參數通過(guò)構造函數傳入

封裝

分析

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

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

所以依據需求情況,Plugin作為首選。

編碼

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

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

新建項目,目錄結構如下。

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

主要文件如下。

  • src

    • index.js:入口函數
    • schema.json:參數校驗
  • util

    • getting.js:常量集合
    • setting.js:函數集合

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

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

將上述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導出一個(gè)函數或類(lèi)
// index.jsmodule.exports = class TinyimgWebpackPlugin {};
函數原型或類(lèi)上綁定apply()訪(fǎng)問(wèn)Compiler對象
// index.jsmodule.exports = class TinyimgWebpackPlugin { apply(compiler) { // Do Something }};
apply()中指定一個(gè)綁定到webpack自身的事件鉤子

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

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

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

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

// 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é)可對比看看哪些細節發(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獲取全部打包文件的對象,篩選出jpgpng,使用map()將單個(gè)圖片數據映射為this.compressImg(file),再通過(guò)Promise.all()操作即可。

整個(gè)業(yè)務(wù)邏輯結合了PromiseAsync/Await兩個(gè)ES6常用特性,它倆組合起來(lái)玩異步編程極其有趣,關(guān)于它倆更多細節可查看筆者這篇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構建流程完成后,所以沒(méi)有什么可返回了,故不作處理。

控制webpack依賴(lài)版本

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

{    'peerDependencies': {        'webpack': '>= 4.0.0',        'webpack-cli': '>= 3.0.0'    }}
總結

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

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

測試

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

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

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

安裝測試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

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

最后在package.json中的scripts插入以下npm scripts,然后執行npm run test調試測試Demo。

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

發(fā)布

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

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

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

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

安裝

npm i -g pkg-master

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

接入

安裝

npm i tinyimg-webpack-plugin

使用
配置功能格式描述
enabled是否啟用功能true/false建議只在生產(chǎn)環(huán)境下開(kāi)啟
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è)零配置開(kāi)箱即用的React/Vue應用自動(dòng)化構建腳手架

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

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

總結

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

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

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

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久