什么是 url-loader
url-loader 會將引入的文件進行編碼,生成 DataURL,相當於把文件翻譯成了一串字符串,再把這個字符串打包到 JavaScript。
使用 base64 來加載圖片也是有兩面性的:
- 優點:節省請求,提高頁面性能
- 缺點:增大本地文件大小,降低加載性能
所以我們得有取舍,只對部分小 size 的圖片進行 base64 編碼,其它的大圖片還是發請求吧。
什么是 file-loader
在css文件中定義background的屬性或者在html中引入image的src,我們知道在webpack打包后這些圖片會打包至定義好的一個文件夾下,和開發時候的相對路徑會不一樣,這就會導致導入圖片路徑的錯誤。而file-loader正是為了解決此類問題而產生的,他修改打包后圖片的儲存路徑,再根據配置修改我們引用的路徑,使之對應引入
之間有什么聯系呢?
url-loader內部封裝了file-loader。url-loader不依賴於file-loader,即使用url-loader時,只需要安裝url-loader即可,不需要安裝file-loader。
通過上面的介紹,我們可以看到,url-loader工作分兩種情況:
- 文件大小小於limit參數,url-loader將會把文件轉為DataURL;
- 文件大小大於limit,url-loader會調用file-loader進行處理,參數也會直接傳給file-loader。因此我們只需要安裝url-loader即可
手寫實現file-loader
file-loader 的工作流程如下:
-
通過 loaderUtils.interpolateName 方法可以根據 options.name 以及文件內容生成一個唯一的文件名 url(一般配置都會帶上hash,否則很可能由於文件重名而沖突)
-
通過 this.emitFile(url, content) 告訴 webpack 我需要創建一個文件,webpack會根據參數創建對應的文件,放在 public path 目錄下。
-
返回 'module.exports = webpack_public_path + '+ JSON.stringify(url) + ‘;’ ,這樣就會把原來的文件路徑替換為編譯后的路徑
對file-loader 中最重要的幾行代碼解釋如下(我們自己來實現一個 file-loader 就只需要這幾行代碼就行了,完全可以正常運行且支持 name 配置):
var loaderUtils = require('loader-utils')
module.exports = function (content) {
// 獲取options,就是 webpack 中對 file-loader 的配置,比如這里我們配置的是 `name=[name]_[hash].[ext]`
// 獲取到的就是這樣一個k-v 對象 { name: "[name]_[hash].[ext]" }
const options = loaderUtils.getOptions(this) || {};
// 這是 loaderUtils 的一個方法,可以根據 name 配置和 content 內容 生成一個文件名。為什么需要 文件內容呢?這是為了保證當文件內容沒有發生變化的時候,名字中的 [hash] 字段也不會變。可以理解為用文件的內容作了一個hash
let url = loaderUtils.interpolateName(this, options.name, {
content
})
this.emitFile(url, content) // 告訴webpack,我要創建一個文件,文件名和內容,這樣webpack就會幫你在 dist 目錄下創建一個對應的文件
// 這里要用到一個變量,就是 __webpack_public_path__ ,這是一個由webpack提供的全局變量,是public的根路徑
// 參見:https://webpack.js.org/guides/public-path/#on-the-fly
// 這里要注意一點:這個返回的字符串是一段JS,顯然,他是在瀏覽器中運行的
// 舉個栗子:
// css源碼這樣寫:background-image: url('a.png')
// 編譯后變成: background-image: require('xxxxxx.png')
// 這里的 require 語句返回的結果,就是下面的 exports 的字符串,也就是圖片的路徑
return 'module.exports = __webpack_public_path__ + '+ JSON.stringify(url)
}
// 一定別忘了這個,因為默認情況下 webpack 會把文件內容當做UTF8字符串處理,而我們的文件是二進制的,當做UTF8會導致圖片格式錯誤。
// 因此我們需要指定webpack用 raw-loader 來加載文件的內容,而不是當做 UTF8 字符串傳給我們
module.exports.raw = true
手寫url-loader
url-loader 的工作流程如下:
- 獲取 limit 參數
- 如果 文件大小在 limit 之類,則直接返回文件的 base64 編碼后內容
- 如果超過了 limit ,則調用 file-loader
- 因為邏輯比較簡單,這里直接放上源碼以及我添加的注釋:
module.exports = function(content) {
// 獲取 options 配置,上面已經講過了就不在重復
var options = loaderUtils.getOptions(this) || {};
// Options `dataUrlLimit` is backward compatibility with first loader versions
// limit 參數,只有文件大小小於這個數值的時候我們才進行base64編碼,否則將直接調用 file-loader
var limit = options.limit || (this.options && this.options.url && this.options.url.dataUrlLimit);
if(limit) {
limit = parseInt(limit, 10);
}
var mimetype = options.mimetype || options.minetype || mime.lookup(this.resourcePath);
// No limits or limit more than content length
if(!limit || content.length < limit) {
if(typeof content === "string") {
content = new Buffer(content);
}
// 直接返回 base64 編碼的內容
return "module.exports = " + JSON.stringify("data:" + (mimetype ? mimetype + ";" : "") + "base64," + content.toString("base64"));
}
// 超過了文件大小限制,那么我們將直接調用 file-loader 來加載
var fallback = options.fallback || "file-loader";
var fallbackLoader = require(fallback);
return fallbackLoader.call(this, content);
}
// 一定別忘了這個,因為默認情況下 webpack 會把文件內容當做UTF8字符串處理,而我們的文件是二進制的,當做UTF8會導致圖片格式錯誤。
// 因此我們需要指定webpack用 raw-loader 來加載文件的內容,而不是當做 UTF8 字符串傳給我們
// 參見:https://webpack.github.io/docs/loaders.html#raw-loader
module.exports.raw = true
總結
- file-loader 返回的是文件的路徑
- url-loader 返回的是文件的base64編碼
參考文獻
- 詳解webpack url-loader和file-loader:https://segmentfault.com/a/1190000018987483
-
webpack 源碼解析:file-loader 和 url-loader:https://www.cnblogs.com/shiyunfront/articles/8944940.html
歡迎關注前端早茶,與廣東靚仔攜手共同進階
前端早茶專注前端,一起結伴同行,緊跟業界發展步伐~
公眾號作者:廣東靚仔