本篇基於圖片加載的基礎來簡單介紹require, require.context和import的區別!
現如今的前端項目用webpack打包已經成爲了行業趨勢,然而在此模式的前提下,圖片的引入是我們不可避免的問題。正常的圖片引入是用img標籤或者元素背景圖的方式,採用這種方式的圖片,webpack都能正常打包並顯示。但是如果直接在js文件中定義圖片路徑,並賦給圖片元素的話不能正常顯示的,這是因爲webpack打包後,會將靜態資源文件放在dist/static/img下,我們的網站實際上以dist目錄作爲根目錄,並由此加載該目錄下的index.html所需的css、js、img等。而當我們在js文件中動態引入圖片時url-loader是無法探測到圖片路徑的。我們build後發現,圖片根本不會打包輸出到dist目錄(webpack是按需打包的)。
此處介紹了三種動態引入圖片的方式:
1. require
1 2 |
// html <img :src="imgList[0]" /> |
1 2 3 4 5 6 7 |
// xxx.js let imgList = [ require('../images/a.png'), require('../images/b.jpg') ] |
官方文檔:如果你的 request 含有表達式(expressions),會創建一個上下文(context),因爲在編譯時(compile time)並不清楚具體是哪一個模塊被導入。
原因:
- webpack本身是一個預編譯路徑 不能require純變量的打包工具,無法預測未知變量路徑
- require(path) ,path 至少要有三部分組成, 目錄,文件名和後綴
- 目錄:webpack 才知道從哪裏開始查找
- 後綴 文件後綴,必須要加上,不然會報錯
- 文件名:可用變量表示
1. 錯誤引用
上述意思即是不能通過以下這種方式加載圖片,這種方式下,webpack找不到具體是哪個模塊(圖片)被引入,故而無法將圖片hash並輸出到dist文件下。
1 2 3 |
let imgUrlStr = '../images/a.png'; let imgUrl = require(imgUrlStr); |
2. 正確引用
鑑於require在純變量的情況下找不到模塊,所以我們至少要在require參數中寫明一個目錄(如下邊代碼中的example 2和example 3),這樣的話,雖然不知道具體的模塊,但是webpack也會爲我們做些分析工作:
-
- 分析目錄: ‘../images’
-
- 提取正則表達式 : ‘/^.*.png$/‘
但是此種情況下,webpack生成的上下文模塊(context module)。它包含目錄下的所有模塊的引用,是通過一個 request 解析出來的正則表達式,去匹配目錄下所有符合的模塊,然後都 require 進來。此 context module 包含一個 map 對象,會把 request 中所有模塊翻譯成對應的模塊 id。這意味着 webpack 能夠支持動態地 require,但會導致所有可能用到的模塊都包含在 bundle 中。
1 2 3 4 5 6 7 8 9 |
let imgName = 'a'; let imgAllName = 'a.png'; // example 1 let imgUrl = require('../images/a.png'); // 純字符串 // example 2 let imgUrl = require('../images/' + imgAllName); // 目錄 + 文件全名 // example 3 let imgUrl = require('../images/' + imgName + '.png'); // 目錄 + 文件名 + 後綴 |
2. require.context
此方法可理解爲require方法的詳細實現,用require.context() 函數來創建自己的 context。可以給這個函數傳入三個參數:一個要搜索的目錄,一個標記表示是否還搜索其子目錄, 以及一個匹配文件的正則表達式。
1 2 3 4 5 6 |
// 語法 require.context(directory, useSubdirectories = false, regExp = /^.//); // example // 創建出一個 context,其中所有文件都來自父文件夾及其所有子級文件夾,request 以 `.png` 結尾。 require.context('../images', true, /.png$/); |
1. require.context返回值
一個 context module 會導出一個(require)函數,此函數可以接收一個參數:request。
此導出函數有三個屬性:resolve, keys, id。
- resolve 是一個函數,它返回 request 被解析後得到的模塊 id。
- keys 也是一個函數,它返回一個數組,由所有可能被此 context module 處理的請求(譯者注:參考下面第二段代碼中的 key)組成。
- id 是 context module 裏面所包含的模塊 id. 它可能在你使用 module.hot.accept 時會用到。
2. 圖片預加載
動態加載文件夾下所有圖片實例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// example // 圖片預加載, preloadAllImages () { let imgCounts = 0; // 已加載圖片計數,可實現真實進度條 let imgsFun = require.context('../images', true, /.(png|jpg)$/); let imgKeys = imgsFun.keys(); imgKeys.forEach(item => { let Img = new Image(); Img.src = imgsFun(item); Img.onload = function () { imgCounts++; } Img.onerror = function () { imgCounts++; }; }); } |
3. import
require是運行時加載模塊,但import命令會被javascript引擎靜態分析,先於模塊內其他模塊執行,做不到運行時加載,因此爲了實現類似於require的動態加載,就提出了實現一個import()函數方法,
1 |
import(specifier); |
上面代碼中,import函數的參數specifier,指定所要加載的模塊的位置。import命令能夠接受什麼參數,import()函數就能接受什麼參數,兩者區別主要是後者爲動態加載。
import()函數可以用在任何地方,不僅僅是模塊,非模塊的腳本也可以使用。它是運行時執行,也就是說,什麼時候運行到這一句,也會加載指定的模塊。另外,import()函數與所加載的模塊沒有靜態連接關係,這點也是與import語句不相同。
import() 特性依賴於內置的 Promise。如果想在低版本瀏覽器使用 import(),記得使用像 es6-promise 或者 promise-polyfill 這樣 polyfill 庫,來預先填充(shim) Promise 環境。
1 2 3 4 5 6 7 |
// example let imgUrl = ''; // 與require參數類似,不能通過純參數的方式引入模塊。正確的引入方式可查看以上require的引入方式 import('../assets/tree/tree.png').then(res => { imgUrl = res; }); |