曾經發表過一篇性能優化的文章《前端性能優化指南》,筆者總結了一些在項目開發過程當中使用過的性能優化經驗。說句真話,性能優化可能在面試過程當中會有用,實際在項目開發過程當中可能沒幾個同窗會注意這些性能優化的細節。css
若常常關注性能優化的話題,可能會發現不管怎樣對代碼作最好的優化也不及對一張圖片作一次壓縮好
。因此壓縮圖片成了性能優化裏最多見的操做,無論是手動壓縮圖片仍是自動壓縮圖片,在項目開發過程當中必須得有。html
自動壓縮圖片一般在webpack
構建項目時接入一些第三方Loader&Plugin
來處理。打開Github
,搜素webpack image
等關鍵字,Star最多仍是image-webpack-loader
和imagemin-webpack-plugin
這兩個Loader&Plugin
。不少同窗可能都會選擇它們,方便快捷,簡單易用,無腦接入。前端
但是,這兩個Loader&Plugin
存在一些特別問題,它們都是基於imagemin
開發的。imagemin
的某些依賴託管在國外服務器,在npm i xxx
安裝它們時會默認走GitHub Releases
的託管地址,若不是規範上網,大家是不可能安裝得上的,即便規範上網也不必定安裝得上。因此筆者又刨根到底發表了一篇關於NPM鏡像處理的文章《聊聊NPM鏡像那些險象環生的坑》,專門解決這些由於網絡環境而致使安裝失敗的問題。除了這個安裝問題,imagemin
還存在另外一個大問題,就是壓縮質感損失得比較嚴重,圖片體積越大越明顯,壓縮出來的圖片總有幾張是失真的,並且整體壓縮率不是很高。這樣在交付項目時有可能被細心的QA小姐姐抓個正着,怎麼和設計圖對比起來不清晰啊!node
此時可能有些同窗已轉戰到手動壓縮圖片了。比較好用的圖片壓縮工具無非就是如下幾個,如有更好用的工具麻煩在評論裏補充喔!同時筆者也整理出它們的區別,供各位同窗參考。webpack
工具 | 開源 | 收費 | API | 免費體驗 |
---|---|---|---|---|
QuickPicture | ✖️ | ✔️ | ✖️ | 可壓縮類型較多,壓縮質感較好,有體積限制,有數量限制 |
ShrinkMe | ✖️ | ✖️ | ✖️ | 可壓縮類型較多,壓縮質感通常,無數量限制,有體積限制 |
Squoosh | ✔️ | ✖️ | ✔️ | 可壓縮類型較少,壓縮質感通常,無數量限制,有體積限制 |
TinyJpg | ✖️ | ✔️ | ✔️ | 可壓縮類型較少,壓縮質感很好,有數量限制,有體積限制 |
TinyPng | ✖️ | ✔️ | ✔️ | 可壓縮類型較少,壓縮質感很好,有數量限制,有體積限制 |
Zhitu | ✖️ | ✖️ | ✖️ | 可壓縮類型通常,壓縮質感通常,有數量限制,有體積限制 |
從上述表格對比可看出,免費體驗都會存在體積限制
,這個可理解,即便收費也同樣,畢竟每一個人都上傳單張10多M的圖片,哪一個服務器能受得了。再來就是數量限制
,一次只能上傳20張,好像有個規律,壓縮質感好就限制數量,不然就不限制數量,固然收費後就沒有限制了。再來就是可壓縮類型
,圖片類型通常是jpg
、png
、gif
、svg
和webp
,gif
壓縮後通常都會失真,svg
一般用在矢量圖標上不多用在場景圖片上,webp
因爲兼容性問題不多被使用,故能壓縮jpg
和png
就足夠了。固然壓縮質感
是最優考慮,綜上所述,大部分同窗都會選擇TinyJpg和TinyPng,其實它倆就是兄弟,出自同一廠商。git
在筆者公衆號的微信討論羣裏發起了一個簡單的投票,最終仍是TinyJpg和TinyPng勝出。es6
手動
jpg
和png
20
張5M
TinyJpg/TinyPng使用智能有損壓縮技術將圖片體積下降,選擇性地減小圖片中類似顏色,只需不多字節就能保存數據。對視覺影響幾乎不可見,可是在文件體積上就有很大的差異。而使用到智能有損壓縮技術
被稱爲量化。github
TinyJpg/TinyPng在壓縮png文件
時效果更顯著。掃描圖片中類似顏色並將其合併,經過減小顏色數量將24位png文件
轉換成體積更小的8位png文件
,丟棄全部沒必要要的元數據。web
大部分png文件
都有50%~70%
的壓縮率,即便視力再好也很難區分出來。使用優化過的圖片可減小帶寬流量和加載時間,整個網站使用到的圖片經TinyJpg/TinyPng壓縮一遍,其成效是再多的代碼優化也沒法追趕得上的。面試
查閱相關資料,發現TinyJpg/TinyPng暫時還未開源其壓縮算法,不過提供了適合開發者使用的API。有興趣的同窗可到其開發API文檔瞧瞧。
在Node
方面,TinyJpg/TinyPng官方提供了tinify做爲壓縮圖片的核心JS庫,使用很簡單,看文檔吧。但是換成開發API仍是逃不過收費,你是想包月呢仍是免費呢,想免費的話就繼續往下看,土豪隨意!
筆者也是常用TinyJpg/TinyPng的程序猿,收費,那是不可能的😂。尋找突破口,解決問題,是做爲一位程序猿最基本的素養。咱們需明確什麼問題,需解決什麼問題
。
從上述得知,只需對TinyJpg/TinyPng原有功能改形成如下功能。
jpg
和png
5M
自動處理
對於前端開發者來講,這種無腦的上傳下載操做必須得自動化,省事省心省力。可是這個操做得結合webpack
來處理,究竟是開發成Loader
仍是Plugin
,後面再分析。不過細心的同窗看標題就知道用什麼方式處理了。
壓縮類型
gif
壓縮後通常都會失真,svg
一般用在矢量圖標上不多用在場景圖片上,webp
因爲兼容性問題不多被使用,故能壓縮jpg
和png
就足夠了。在過濾圖片時,使用path模塊
判斷文件類型是否爲jpg
和png
,是則繼續處理,不然不處理。
數量限制
數量限制固然是不能存在的,萬一項目裏超過20張圖片,那不是得分批處理,這個不能有。對於這種無需登陸狀態就能處理一些用戶文件的網站,一般都會經過IP來限制用戶的操做次數。有些同窗可能會說,刷新頁面不就好了嗎,每次壓縮20張圖片,再刷新再壓縮,萬一有500張圖片呢,你就刷新25次嗎,這樣很好玩是吧!
因爲大多數Web架構不多會將應用服務器直接對外提供服務,通常都會設置一層Nginx
做爲代理和負載均衡,有的甚至可能有多層代理。鑑於大多數Web架構都是使用Nginx
做爲反向代理,用戶請求不是直接請求應用服務器的,而是經過Nginx設置的統一接入層將用戶請求轉發到服務器的,因此可經過設置HTTP請求頭字段X-Forwarded-For
來僞造IP。
X-Forwarded-For指用來識別經過代理
或負載均衡
的方式鏈接到Web服務器的客戶端最原始的IP地址的HTTP請求頭字段。固然,這個IP也不是一成不變的,每次請求都需隨機更換IP,騙過應用服務器。若應用服務器增長了僞造IP識別,那可能就沒法繼續使用隨機IP了。
體積限制
體積限制這個能理解,也不必搞一張那麼大的圖片,多浪費帶寬流量和加載時間啊。在上傳圖片時,使用fs模塊
判斷文件體積是否超過5M
,是則不上傳,不然繼續上傳。固然,交給TinyJpg/TinyPng接口判斷也行。
輸出信息
壓縮成功與否得讓別人知道,輸出原始大小、壓縮大小、壓縮率和錯誤提示等,讓別人清楚這些處理信息。
經過上述抽絲剝繭的分析,那麼就開始着手編碼了。
隨機生成HTTP請求頭
既然可經過X-Forwarded-For
來僞造IP,那麼得有一個隨機生成HTTP請求頭字段的函數,每次請求接口時都隨機生成相關的請求頭字段。打開tinyjpg.com或tinypng.com上傳一張圖片,經過Chrome DevTools
分析Network
發現其請求接口是web/shrink
。另外每次請求也不要集中在單一的hostname
上,隨機派發到tinyjpg.com
或tinypng.com
上會更好。經過封裝RandomHeader
函數隨機生成請求頭信息,後續使用https模塊
以RandomHeader()
生成的配置做爲入參進行請求。
trample
是筆者開發的一個Web/Node
通用函數工具庫,包含常規的工具函數,助你少寫更多通用代碼。詳情請查看文檔,順便給一個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
同步化異步代碼。如下函數的具體斷點調試就不說了,有興趣的同窗自行調試函數的入參和出參哈!
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(); }); }
壓縮圖片
經過上傳圖片
函數獲取壓縮後的圖片信息,再依據圖片信息經過下載圖片
函數生成本地文件。
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。
若壓縮指定文件夾裏符合條件的全部圖片,可經過fs模塊
獲取圖片並使用map()
將單個圖片路徑映射爲CompressImg(path)
,再經過Promise.all()
操做便可。在這裏就不貼代碼了,看成思考題,自行完成。
將上述壓縮圖片的功能封裝成Loader
仍是Plugin
呢?接下來會一步一步分析。
webpack
是一個前端資源打包工具,它根據模塊依賴關係進行靜態分析,而後將這些模塊按照指定規則生成對應的靜態資源。
網上一大堆webpack
教程,筆者就再也不花大篇幅囉嗦了,相信各位同窗都是一位標準的Webpack配置工程師
。如下簡單回顧一次webpack
的組成、構建機制和構建流程,相信也能從這些知識點中定位出Loader
和Plugin
在Webpack構建流程
中是處於一個什麼樣的角色地位。
本文所說的webpack都是基於webpack v4
組成
構建機制
bundle.js
中構建流程
初始
初始參數
:合併命令行和配置文件的參數編譯
執行編譯
:依據參數初始Compiler對象
,加載全部Plugin
,執行run()
肯定入口
:依據配置文件找出全部入口文件編譯模塊
:依據入口文件找出全部依賴模塊關係,調用全部Loader
進行轉換生成圖譜
:獲得每一個模塊轉換後的內容及其之間的依賴關係輸出
輸出資源
:依據依賴關係將模塊組裝成塊再組裝成包(module → chunk → bundle
)生成文件
:依據配置文件將確認輸出的內容寫入文件系統Loader用於轉換模塊源碼,筆者將其翻譯爲轉換器
。Loader
可將全部類型文件轉換爲webpack
可以處理的有效模塊,而後利用webpack
的打包能力對它們進行二次處理。
Loader
具備如下特色:
只完成一種轉換
)Loader
將全部類型文件轉換爲應用程序的依賴圖譜可直接引用的模塊,因此Loader
可用於編譯一些文件,例如pug → html
、sass → css
、less → css
、es5 → es6
、ts → js
等。
處理一個文件可以使用多個Loader
,Loader
的執行順序和配置順序是相反的,即末尾Loader
最早執行,開頭Loader
最後執行。最早執行的Loader
接收源文件內容做爲參數,其它Loader
接收前一個執行的Loader
的返回值做爲參數,最後執行的Loader
會返回該文件的轉換結果。一句話歸納:富土康流水線廠工。
Loader
開發思路總結以下:
module.exports
導出一個函數
source
(源文件內容)return
返回最終轉換結果(字符串形式)編寫Loader時要遵循單一職責原則,每一個Loader只作一種轉換工做
Plugin用於擴展執行範圍更廣的任務,筆者將其翻譯爲擴展器
。Plugin
的範圍很廣,在Webpack構建流程
裏從開始到結束都能找到時機做爲插入點,只要你想不到沒有你作不到。因此筆者認爲Plugin
的功能比Loader
更增強大。
Plugin
具備如下特色:
webpack
運行生命週期中廣播的事件webpack
提供的API改變輸出結果webpack
的Tapable事件流機制保證Plugin的有序性在webpack
運行生命週期中會廣播出許多事件,Plugin
可監聽這些事件並在合適時機經過webpack
提供的API改變輸出結果。在webpack
啓動後,在讀取配置過程當中執行new MyPlugin(opts)
初始化自定義Plugin
獲取其實例,在初始化Compiler對象
後,經過compiler.hooks.event.tap(PLUGIN_NAME, callback)
監聽webpack
廣播事件,當捕抓到指定事件後,會經過Compilation對象
操做相關業務邏輯。一句話歸納:本身看着辦。
Plugin
開發思路總結以下:
module.exports
導出一個函數或類
函數原型或類
上綁定apply()
訪問Compiler對象
apply()
中指定一個綁定到webpack
自身的事件鉤子webpack
提供的API處理資源(可引入第三方模塊擴展功能)webpack
提供的方法返回該資源傳給每一個Plugin的Compiler和Compilation都是同一個引用,若修改它們身上的屬性會影響後面的Plugin,因此需謹慎操做
本質
Loader
本質是一個函數,轉換接收內容,返回轉換結果Plugin
本質是一個類,監聽webpack
運行生命週期中廣播的事件,在合適時機經過webpack
提供的API改變輸出結果配置
Loader
在module.rule
中配置,類型是數組,每一項對應一個模塊解析規則Plugin
在plugin
中配置,類型是數組,每一項對應一個擴展器實例,參數經過構造函數傳入從上述可知Loader
和Plugin
在角色定位和執行機制上有不少不同,到底如何選擇呢?各有各好,固然仍是需分析後進行選擇。
Loader
在webpack
中扮演着轉換器的角色,用於轉換模塊源碼,簡單理解就是將文件轉換成另外形式的文件,而本文主題是壓縮圖片
,jpg
壓縮後仍是jpg
,png
壓縮後仍是png
,在文件類型上來講仍是沒有變化。Loader
的轉換過程是附屬在整個Webpack構建流程
中的,意味着打包時間包含了壓縮圖片的時間成本,對於追求webpack
性能優化來講實屬有點違背原則。而Plugin
剛好是監聽webpack
運行生命週期中廣播的事件,在合適時機經過webpack
提供的API改變輸出結果,因此可在整個Webpack構建流程
完成後(所有打包文件輸出完成後)插入壓縮圖片的操做。換句話說,打包時間再也不包含壓縮圖片的時間成本,打包完成後該幹嗎就幹嗎,還能幹嗎,壓縮圖片啊。
因此依據需求狀況,Plugin
做爲首選。
依據上述Plugin
開發思路,那麼就開始着手編碼了。
筆者把這個壓縮圖片的Plugin
命名爲tinyimg-webpack-plugin,tinyimg
意味着TinyJpg和TinyPng合體。
新建項目,目錄結構以下。
tinyimg-webpack-plugin ├─ src │ ├─ index.js │ ├─ schema.json ├─ util │ ├─ getting.js │ ├─ setting.js ├─ .gitignore ├─ .npmignore ├─ license ├─ package.json ├─ readme.md
主要文件以下。
src
util
安裝項目所需模塊,和上述compress-img的依賴一致,額外安裝schema-utils
用於校驗Plugin
參數是否符合規定。
npm i chalk figures ora schema-utils trample
封裝常量集合和函數集合
將上述compress-img
的TINYIMG_URL
和RandomHeader()
封裝到工具集合中,其中常量集合增長IMG_REGEXP
和PLUGIN_NAME
兩個常量。
// getting.js const 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.js const { 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 };
經過
module.exports
導出一個函數或類
// index.js module.exports = class TinyimgWebpackPlugin {};
在函數原型或類
上綁定apply()
訪問Compiler對象
// index.js module.exports = class TinyimgWebpackPlugin { apply(compiler) { // Do Something } };
在apply()
中指定一個綁定到webpack
自身的事件鉤子
從上述分析中可知,在所有打包文件輸出完成後插入壓縮圖片的操做,因此應該選擇該時機對應的事件鉤子。從Webpack Compiler Hooks API文檔中可發現,emit
正是這個Plugin
所需的事件鉤子。emit
在生成資源到輸出目錄前執行,此刻可獲取全部圖片文件的數據和輸出路徑。
爲了方便在特定條件下啓用功能
和打印日誌
,因此設置相關配置。
在apply()
中處理相關業務邏輯,可能使用到Plugin
的入參,那麼就得對參數進行校驗。定義一個Plugin
的Schema
,經過schema-utils
來校驗Plugin
的入參。
// schema.json { "type": "object", "properties": { "enabled": { "description": "start plugin", "type": "boolean" }, "logged": { "description": "print log", "type": "boolean" } }, "additionalProperties": false }
// index.js const 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-img
到Plugin
在整合過程當中會有一些小修改,各位同窗可對比看看哪些細節發生了變化。
// index.js const 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(); }); } };
在事件鉤子中經過
webpack
提供的API處理資源
經過compilation.assets
獲取所有打包文件的對象,篩選出jpg
和png
,使用map()
將單個圖片數據映射爲this.compressImg(file)
,再經過Promise.all()
操做便可。
整個業務邏輯結合了Promise
和Async/Await
兩個ES6經常使用特性,它倆組合起來玩異步編程極其有趣,關於它倆更多細節可查看筆者這篇4000點贊量和14萬閱讀量的文章《1.5萬字歸納ES6所有特性》。
// index.js const 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) { ... } };
經過
webpack
提供的方法返回該資源
因爲壓縮圖片的操做是在整個Webpack構建流程
完成後,因此沒有什麼可返回了,故不做處理。
控制
webpack
依賴版本
因爲tinyimg-webpack-plugin
基於webpack v4
,因此需在package.json
中添加peerDependencies
,用來告知安裝該Plugin
的模塊必須存在peerDependencies
裏的依賴。
{ "peerDependencies": { "webpack": ">= 4.0.0", "webpack-cli": ">= 3.0.0" } }
總結
按照上述總結的開發思路一步一步來完成編碼,實際上是挺簡單的。若需開發一些跟本身項目相關的Plugin
,仍是需多多熟悉Webpack Compiler Hooks API文檔,相信各位同窗都能手戳一個完美的Plugin
出來。
tinyimg-webpack-plugin
源碼請戳這裏查看,Star一個如何,嘻嘻。
整個Plugin
開發完成,接下來需走一遍測試流程,看能不能把這個壓縮圖片的擴展器
跑通。相信各位同窗都是一位標準的Webpack配置工程師
,可自行編寫測試Demo驗證大家的Plugin
。
在根目錄下建立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
相關配置模塊。
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
代碼,代碼量有點多,直接貼連接好了,請戳這裏。
最後在package.json
中的scripts
插入如下npm scripts
,而後執行npm run test
調試測試Demo。
{ "scripts": { "test": "webpack --config test/webpack.config.js" } }
發佈到NPM倉庫
上很是簡單,僅需幾行命令。若還沒註冊,趕忙去NPM
上註冊一個帳號。若當前鏡像爲淘寶鏡像
,需執行npm config set registry https://registry.npmjs.org/
切換回源鏡像。
接下來一波操做就可完成發佈了。
cd my-plugin
npm login
npm whoami
npm publish
npm logout
若不想牢記這麼多命令,可用筆者開發的pkg-master
一鍵發佈,若存在某些錯誤會立馬中斷髮布並提示錯誤信息,是一個很是好用的集成建立和發佈的NPM模塊管理工具。詳情請查看文檔,順便給一個Star以做鼓勵。
安裝
npm i -g pkg-master
使用
命令 | 縮寫 | 功能 | 描述 |
---|---|---|---|
pkg-master create |
pkg-master c |
建立模塊 | 生成模塊的基礎文件 |
pkg-master publish |
pkg-master p |
發佈模塊 | 檢測NPM的運行環境 和帳號狀態 ,經過則自動發佈模塊 |
安裝
npm i tinyimg-webpack-plugin
使用
配置 | 功能 | 格式 | 描述 |
---|---|---|---|
enabled |
是否啓用功能 | true/false |
建議只在生產環境下開啓 |
logged |
是否打印日誌 | true/false |
打印處理信息 |
在webpack.config.js
或webpack配置
插入如下代碼。
在CommonJS中使用
const TinyimgPlugin = require("tinyimg-webpack-plugin"); module.exports = { plugins: [ new TinyimgPlugin({ enabled: process.env.NODE_ENV === "production", logged: true }) ] };
在ESM中使用
必須在babel
加持下的Node環境中使用
import TinyimgPlugin from "tinyimg-webpack-plugin"; export default { plugins: [ new TinyimgPlugin({ enabled: process.env.NODE_ENV === "production", logged: true }) ] };
推薦一個零配置開箱即用的React/Vue應用自動化構建腳手架
bruce-cli
是一個React/Vue應用自動化構建腳手架,其零配置開箱即用的優勢很是適合入門級、初中級、快速開發項目的前端同窗使用,還可經過建立brucerc.js
文件來覆蓋其默認配置,只需專一業務代碼的編寫無需關注構建代碼的編寫,讓項目結構更簡潔。使用時記得查看文檔喲,喜歡的話給個Star。
固然,筆者已將tinyimg-webpack-plugin
集成到bruce-cli
中,零配置開箱即用走起。
整體來講開發一個Webpack Plugin
不難,只需好好分析需求,瞭解webpack
運行生命週期中廣播的事件,編寫自定義Plugin
在合適時機經過webpack
提供的API改變輸出結果。
若以爲tinyimg-webpack-plugin
對你有幫助,可在Issue上提出你的寶貴建議
,筆者會認真閱讀並整合你的建議。喜歡tinyimg-webpack-plugin
的請給一個Star,或Fork本項目到本身的Github
上,根據自身需求定製功能。