1. 程式人生 > 程式設計 >webpack 動態批量載入檔案的實現方法

webpack 動態批量載入檔案的實現方法

背景

最近筆者在工作中遇到了一個小需求:

要實現一個元件來播放幀圖片

這個需求本身不復雜,但是需要在元件中一次性引入十張圖片,就像下面這樣:

// 就是這麼任性,下標從0開始~
import frame0 from './assets/frame_0.png'
import frame1 from './assets/frame_1.png'
import frame2 from './assets/frame_2.png'
// ..省略n張
import frame7 from './assets/frame_8.png'
import frame8 from './assets/frame_9.png'
import frame9 from './assets/frame_10.png'

作為一個有程式碼潔癖的程式設計師,我是不允許這種重複性程式碼存在滴,於是乎就嘗試有沒有什麼簡單的方法。

方法一:繞過 webpack

由於筆者用的是 vue-cli 3,熟悉的小夥伴都知道,將圖片以固定的格式放在 public 資料夾下面,然後在程式碼中直接以絕對路徑引入即可。這麼做的話,就可以根據檔名構造一個 url 陣列,簡單程式碼如下:

const frames = []
_.times(10,v => {
  frames.push(`/images/frame_${v}.png`)
})
// 然後你就得到 10個 url 的陣列啦

此方法本身是 vue-cli 提供的一個 應急手段,它有幾個缺點:

  • 無法利用 webpack 處理資源,無法產生內容雜湊,不利於快取更新
  • 無法利用 url-loader 將資源內聯成 base64 字串 以減少網路請求

方法二:require

由於 import 是靜態關鍵字,所以如果想要批量載入檔案,可以使用 require,但是直接像下面這樣寫是不行的:

const frames = []
_.times(10,v => {
  const path = `./assets/images/frame_${v}.png`
  frames.push(require(path))
}

上面的程式碼中的 path 是在程式執行時才能確定的,即屬於 runtime 階段,而 webpack 中的 require 是在構建階段確定檔案位置的,所以 webpack 沒法推測出這個 path 在哪裡。

但是卻可以這樣寫:

const frames = []
_.times(10,v => {
  frames.push(require(`./assets/images/frame_${v}.png`))
}
// frames 中就得到 帶 hash 值的路徑

雖然這兩種寫法在語法上沒有差別,但是第二種寫法在構建時提示了 webpack,webpack 會將 ./assets/images 中的所有檔案都加入到 bundle 中,從而在你執行時可以找到對應的檔案。

在使用方法二的時候筆者嘗試將批量載入的邏輯提取到其他模組用來複用:

export function loadAll (n,prefix,suffix) {
 const frames = []
 _.times(n,v => {
  frames.push(require('./' + prefix + v + suffix))
 })
 return frames
}

但是顯然失敗了,因為提取後的程式碼,執行的 context 屬於另一個模組,所以也就無法找到相對路徑中的檔案。

方法三:require.context

上面兩種方法都不算很優雅,於是就去翻 webpack 的文件,終於,讓我找到了這麼一個方法:require.context

require.context(
 directory: String,includeSubdirs: Boolean /* 可選的,預設值是 true */,filter: RegExp /* 可選的,預設值是 /^\.\/.*$/,所有檔案 */,mode: String /* 可選的,'sync' | 'eager' | 'weak' | 'lazy' | 'lazy-once',預設值是 'sync' */
)

指定一系列完整的依賴關係,通過一個 directory 路徑、一個 includeSubdirs 選項、一個 filter 更細粒度的控制模組引入和一個 mode 定義載入方式。然後可以很容易地解析模組.

我們還是看上面的例子:

const frames = []
const context = require.context('./assets/images',false,/frame_\d+.png/)
context.keys().forEach(k => {
  frames.push(context(k))
})

這裡的程式碼通過 require.context 建立了一個 require 上下文。

  • 第一個引數指定了需要載入的資料夾,即元件當前目錄下的 ./assets/images 資料夾
  • 第二個引數指定是否需要包含子目錄,由於沒有子目錄,所以傳 false
  • 第三個引數指定需要包含的檔案的匹配規則,我們用一個正則表示

然後使用 context.keys() 就能拿到該上下文的檔案路徑列表,而 context 本身也是一個方法,相當於設定過上下文的 require,我們將 require 後的檔案放入陣列中,陣列中的路徑其實是帶 hash 值的,如下是我專案中的圖片:

["/static/img/frame_0.965ef86f.png","/static/img/frame_1.c7465967.png","/static/img/frame_2.41e82904.png","/static/img/frame_3.faef7de9.png","/static/img/frame_4.27ebbe45.png","/static/img/frame_5.d98cbebe.png","/static/img/frame_6.c10859bc.png","/static/img/frame_7.5e9cbdf0.png","/static/img/frame_8.b3b92c71.png","/static/img/frame_9.36660295.png"]

而且如果設定過內聯圖片的話,陣列中可能還有圖片的 base64 串。

重構一下

方法三已經解決了我們的問題,而且可以批量 require 某個資料夾中的檔案。但是 forEach 那塊的邏輯明顯是重複的,所以我們當然提取出來啦,以後多個元件呼叫的時候只需要引入即可:

公共模組:

/**
 * 批量載入幀圖片
 * @param {Function} context - require.context 建立的函式
 * @returns {Array<string>} 返回的所有圖片
 */
function loadFrames (context) {
 const frames = []
 context.keys().forEach(k => {
  frames.push(context(k))
 })
 return frames
}

元件中:

const context = require.context('./assets/images',/frame_\d+.png/)
const frames = loadFrames(context)

大功告成!感興趣的小夥伴可以點選文末連結檢視詳細文件~

參考連結
require.context
webpack dynamic require

到此這篇關於webpack 動態批量載入檔案的實現方法的文章就介紹到這了,更多相關webpack 動態批量載入檔案內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!