這是開頭
最近在試水
Vue CLI 3
,並且嘗試配置一個多頁面(多應用)專案出來,期間又遇到各種路徑問題,於是…於是有了下面的嘮叨。
以下都是基於Vue CLI 3
來舉例說明的,使用2.x
版本的其實也類似
首先,參考 官方文件對靜態資源處理的說明,並通過自己的實踐,可以總結出以下內容
靜態資源可以通過兩種方式進行處理:
-
以下情況下,資源不會被 webpack 處理,而是被直接拷貝:
- 放置在 public 目錄下,即使未被使用。
- 通過絕對路徑被引用,即以
/
開頭的路徑。
-
以下情況下,資源會被 webpack 處理(URL的resolve、minify、uglify、轉 base64 等):
- 使用
JavaScript
匯入。 - 在
template/CSS
中通過相對路徑(即以.
開頭或直接以檔案(夾)名開頭)被引用。 - URL 以
~
開頭,其後的任何內容都會作為一個模組請求被解析。 - URL 以
@
開頭,它也會作為一個模組請求被解析(@
是在 webpack 設定的alias
)。
- 使用
我們應該根據實際情況去選擇我們要引用的資源是否要被處理,然後用對應的、正確的方式去引用它們以達到目的。以下對使用絕對路徑和相對路徑的方法和注意事項進行描述。
使用絕對路徑
預設情況下,Vue CLI 會假設你的應用是被部署在一個域名的根路徑上(對應選項 baseUrl: `/`
),例如 https://www.my-app.com/
。如果應用被部署在一個子路徑上,你就需要用這個選項指定這個子路徑。例如,如果你的應用被部署在 https://www.my-app.com/my-app/
,則設定 baseUrl
為 /my-app/
。正因為以上的可能情況,我們應該在打算引用純靜態資源(那些不被webpack處理的資源,一般就是 public
目錄下的資源)的時候,都確保使用 baseUrl
作為 URL 的開頭,以下列舉在不同檔案中配合 baseUrl
選項寫絕對路徑的使用方法和注意事項:
在入口html檔案中使用
我們可以使用 lodash template 語法插入 baseUrl
:
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
在 *.vue
中使用
我們可以通過 Vue CLI
提供的客戶端環境變數 process.env.BASE_URL
來獲取 baseUrl
:
/* 在需要的元件中定義 baseUrl,然後在 <template> 下使用 */
<template>
<div id="app">
<img :src="imgUrl">
<img :src="`${baseUrl}imgs/my_image.png`">
</div>
</template>
<script>
export default {
name: `App`,
data() {
return {
baseUrl: process.env.BASE_URL,
isBigImg: Math.random() > 0.5
}
}
computed: {
// 動態地獲取不同的靜態資源
imgUrl() {
if (this.isBigImg) {
return `${baseUrl}imgs/my_image_big.png`
} else {
return `${baseUrl}imgs/my_image.png`
}
}
}
};
</script>
/* 個人建議可以在全域性定義,減去在每個元件內定義的麻煩
Vue.prototype.$baseUrl = process.env.BASE_URL
// 在 <template> 下使用
<img :src="`${$baseUrl}imgs/my_image.png`">
在其他 js 模組中使用
import axios from `axios`;
const baseUrl = process.env.BASE_URL;
axios.defaults.baseURL = `http://www.example.com${baseUrl}api/`
在樣式檔案中使用(以 sass/scss
為例)
因為 sass
檔案中無法獲取環境變數或 webpack 內的配置,於是最直接的方法就是自定義一個變數,然後在每個需要使用到它的檔案引用它。
// config.scss
$baseUrl: "/";
// icon.scss
@import "config"
.icon-test {
display: inline-block;
background: url($baseUrl + `imgs/icon_test.png`) no-repeat;
width: 10px;
height: 10px;
}
這樣做還是有比較大的麻煩:
- 如果生產環境和開發環境的
baseUrl
不同,每次轉換環境去編譯都要去手動修改這個變數,十分之麻煩而且可能出現錯誤; - 兩處地方相同的定義,不方便程式碼的維護;
- 在後續講到的關於 相對路徑 的坑會涉及到,每次引用
config.scss
的路徑並不一定是一樣的,且很容易出現編譯錯誤;
那麼,有沒有什麼辦法能避免人工操作、避免多次的定義並且避免使用可能潛在錯誤的引用呢?幸虧的確是有的! sass-loader
提供了一個 data
選項,可以為全域性注入變數或樣式檔案;
// vue.config.js
const baseUrl = process.env.NODE_ENV === `production` ? `/sub/` : `/`;
module.exports = {
baseUrl,
css: {
loaderOptions: {
sass: {
data: `$baseUrl: "${baseUrl}";`
}
}
}
}
這樣我們就可以在全域性的 `sass` 檔案中使用 `$baseUrl` 這個變數了,而且在只定義一次的情況下,能根據編譯環境變化而變化。
使用相對路徑
使用相對路徑也會存在一些坑,接下來會列舉常見的關於相對路徑的坑與解決方法:
JavaScript
動態引用資源,編譯沒報錯,但頁面上請求返回 404
有時候我們需要使用 JavaScript
動態的引用某些資源,且希望這些資源被 webpack 一同打包,我們先看這種做法:
computed: {
background () {
return `./bgs/${this.id}.jpg`
}
}
我們會發現打包沒報錯,但是在頁面上可以發現這些資源的請求都是 404
。這是因為類似 ./bgs/${this.id}.jpg
這樣的動態字串在打包階段不會被 webpack 識別為依賴,資源也就不會被打包了。為了讓 webpack 識別這些依賴,我們可以這樣做:
computed: {
background () {
return require(`./bgs/` + this.id + `.jpg`)
}
}
通過使用 require()
讓 webpack 將括號內的 URL 識別為一個依賴並傳入對應的 loader
進行處理。
要特別注意,以上的例子中,
./bgs/
目錄下的所有圖片都會被打包,因為 webpack 無法得知頁面在執行時會使用哪張圖片,所以 webpack 會把所有的圖片都打包了。
在 sass 中使用相對路徑引用圖片或字型檔案,編譯報錯
先來看一個例子:
// 檔案目錄
// src
// |--assets
// | |
// | |-fonts
// | | |- iconfont.eot
// | |
// | |-css
// | |
// | |-iconfont.scss
// |
// |--app.vue
// iconfont.scss
@font-face {
font-family: "iconfont";
src: url("../fonts/iconfont.eot");
...
}
// app.vue
<style lang="scss">
@import `./assets/css/iconfont.scss`
</style>
往往我們在打包的時候會報錯(以上例子會報錯),說找不到 iconfont.eot
。 sass-loader
文件中有對 url()
進行了單獨的說明:
Since Sass/libsass does not provide url rewriting, all linked assets must be relative to the output.
- If you`re just generating CSS without passing it to the css-loader, it must be relative to your web root.
- If you pass the generated CSS on to the css-loader, all urls must be relative to the entry-file (e.g. main.scss).
大致意思就是, sass-loader
並不提供 url 的重寫,所有的 scss
檔案被 sass-loader
處理成最終的 CSS
後(編譯過程中 url
不會被重寫即保持原樣),再傳遞給 css-loader
處理。也就是說,所有的 url
都是相對於輸出的!在 Vue CLI
搭建的專案中,它們都是相對於使用這些 scss
檔案的 vue
檔案的。對於上例,是相對於 app.vue
的,因此報錯。我們會很自然的會希望路徑的引用是相對於 scss
檔案本身的,sass-loader
文件中也給出瞭解決方案:
- Add the missing url rewriting using the resolve-url-loader. Place it before the sass-loader in the loader chain.
- Library authors usually provide a variable to modify the asset path. bootstrap-sass for example has an $icon-font-path. Check out this working bootstrap example.
第一個方法:使用 resolve-url-loader 來彌補 sass-loader
缺失的 url 重寫功能,注意要放到 sass-loader
以前呼叫。
第二個方法:Library 作者一般都會提供變數,用來設定資源路徑,如 bootstrap-sass 可以通過 $icon-font-path 來設定。參見this working bootstrap example。
這樣看來解決的思路有兩種:
- 寫 url 的時候就寫
vue
檔案相對於資源的路徑。這種方法較為暴力,當專案層級複雜了之後容易寫錯路徑(加上現有的編輯器、IDE應該認為你寫的路徑是錯誤的)。當同個scss
檔案被多個不同層級的vue
檔案引用的時候,這種暴力的方法就行不通了! - 使用第三方庫補充
sass-loader
的路徑重寫功能,讓路徑的引用是相對於當前scss
檔案本身的。這個方法能較好的解決問題。
在這裡提供一下我喜歡的方法。與其考慮 讓路徑的引用是相對於 scss
檔案本身 或 讓路徑直接相對於 vue
檔案,我們可以換個思路,讓所有路徑都是以根目錄往下找,並讓 webpack 對路徑進行重寫,但是直接用 /src/
這種絕對路徑的寫法會讓這些資源不被 webpack 打包。在前文提及到的,webpack 有個強大的機制,也就是 ~
,通過在 url 前面新增 ~
可以告訴 webpack 要把它當做一個模組來處理,也就是會被 webpack 打包。配合 webpack 提供的別名 @
(/src
),我們可以對上例做修改:
// iconfont.scss
@font-face {
font-family: "iconfont";
src: url("~@/assets/fonts/iconfont.eot");
...
}
這樣子,通過 webpack 對模組的處理,可以正確通過編譯!這樣做的好處是可大大避免書寫相對路徑可能產生的錯誤,每次只需“無腦”從根目錄往下找就是了,又可以減小依賴、減少配置項。
如果有什麼地方我理解錯誤歡迎大家指出!