在 Vue 開發中,假如我們手上有幾張編號編好的素材圖片,放在專案的/src/assets/資料夾中。要將它們依序顯示,正常思路下會用v-for寫出:

1
<img v-for="i in 3" :src="`./assets/cat${i}.jpg`">

結果在 dev-server 圖片就出不來,console 給的訊息是GET http://localhost:8080/assets/cat1.jpg 404 (Not Found),初步看來是 Webpack 的 file-loader 不認動態綁定的 src …

當然可以不要用v-for,無腦交差了事…

1
2
3
<img src="./assets/cat1.jpg">
<img src="./assets/cat2.jpg">
<img src="./assets/cat3.jpg">

有點 sense 的工程師馬上能嗅出這違反DRY原則。而且需求不會最奇葩、只有更奇葩,假如下來的規格是搭配 Bootstrap 框架,做一組單選 radio ,每個選項值用 v-model 綁定,label 裡必須是對應編號的圖片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="custom-control custom-radio custom-control-inline mb-2">
<input class="custom-control-input" type="radio" value="1" id="cat1" v-model="catChosen">
<label class="custom-control-label" :for="cat1">
<img src="./assets/cat1.jpg">
</label>
</div>
<div class="custom-control custom-radio custom-control-inline mb-2">
<input class="custom-control-input" type="radio" value="2" id="cat2" v-model="catChosen">
<label class="custom-control-label" :for="cat2">
<img src="./assets/cat2.jpg">
</label>
</div>
...

又臭又長,正常的前端到此會出現頭暈、噁心的臨床反應。更別說今天 UI 好心只給3張圖,哪天需求變成20張圖就能靠代碼行數充充 KPI 呢。

於是開始估狗與嘗試,如何用動態的寫法讓 Webpack 也能正常編譯出來。

靜態資源

一種做法是把素材全當成靜態資源,全部放到與 src 同層級的/static/資料夾下。

1
2
3
4
5
└ vue-project
├ dist/
├ src/
├ static/
└ ...
1
<img v-for="i in 3" :src="`static/cat${i}.jpg`">

此時圖片就正常顯示了。不過要照開發情境取捨利弊,因為這失去使用 Webpack 幫我們打包的意義,例如圖檔路徑就沒有 hash 指紋讓瀏覽器快取判斷更新時機,或者無法搭配 url-loader 將圖片轉成 base64,參考將檔案放在 static 與 src/assets 的差異

require()

之後爬了一些文,算是找到比較正統的作法,只要把圖片路徑用require()去 call 就好:

1
<img v-for="i in 3" :src="require(`./assets/cat${i}.jpg`)">

也可以把它包成 method

template
1
<img v-for="i in 3" :src="catImg(i)">
script
1
2
3
4
5
methods: {
catImg: function(i) {
return require(`./assets/cat${i}.jpg`);
}
}

圖片也成功編譯到/dist資料夾,路徑帶有 hash 指紋

只是這方法也有奇怪、但影響不大的 bug … 例如我另外加了 cat4.jpg, cat5.jpg 當成沒被用到的素材,然後npm run build

各位觀眾,五隻貓!

跟預想的行為有出入,檔案只用到三張圖卻五張貓都抓進去了,不過沒用到的 logo.png 倒是很正常地沒被編譯,搞不清楚是 file-loader 還是 v-for 的問題,活著好難心好累,至少缺點比用靜態參照的方式還小、可以接受,看日後小精靈會不會幫我找到解答。

附上相關連結