在 vue-loader工作流程梳理 里我們提到,vue-loader 編譯的一環(huán)中(樣式部分會應用到 css-loader )<style>
和<template>
中引用的資源會被轉換成模塊請求赃承,即require('xxx.png')
的形式域帐。而 file-loader 則會將資源文件復制到指定的打包目錄赘被,同時把原本的模塊引用(import/require()
)解析重寫為輸出文件的正確訪問路徑(url
)。
資源文件輸出路徑/訪問路徑
簡單來說肖揣,file-loader
主要解決兩件事:
1 指定輸出文件的路徑——即打包后文件的存儲位置民假。
2 生成解析文件的路徑——即打包后引用文件時的URL地址。
開發(fā)階段在css或html標簽中引用的資源路徑龙优,通常和項目打包后資源的訪問路徑不一樣羊异。因此在配置 file-loader 的過程中我們要把握和厘清輸出目錄outputPath
和引用路徑前綴publicPath
這兩項(可以按照 webpack 的output.path
,output.publicPath
的機制來理解)彤断,不然可能導致項目運行時圖片報404錯誤球化。
vue-cli4 的默認配置下,圖片文件都會被輸出在/dist/static/img
目錄瓦糟,同時引用地址會被解析成絕對路徑重寫入url中筒愚。像這樣:background-image: url(/static/img/denglun-bg.ba926c29.jpg)
。但是絕對路徑不夠靈活菩浙,比如用 nginx 配置 HTTPS 服務 時將項目部署在二級目錄下巢掺,直接訪問根目錄肯定會出錯。
開發(fā)環(huán)境用絕對路徑不會有問題劲蜻,而生產(chǎn)環(huán)境最終通過 mini-css-extract-plugin 把每個.vue
內的樣式都提取到單獨的.css
文件中陆淀。然后通過正確配置插件的publicPath
,或者 file-loader 的 publicPath
將 url 寫成相對路徑先嬉。
注??:一般測試環(huán)境打包(vue-cli-service serve
)也會輸出打包文件到我們配置的dist目錄轧苫,只是都在內存中不可見罷了
file-loader 的配置項詳解,即傳遞給 options 的參數(shù)疫蔓。
-
outputPath 資源打包輸出時存放的目錄(相對于打包目錄的路徑)
默認值為 undefined含懊,即直接輸出在 dist (默認的打包目錄) 下最終導出的文件路徑為
webpackConfig.output.path
+file-loader.outputPath
+file-loader.name
若 file-loader 的配置為{ outputPath: 'static/img', file-loader.name: '[name].[contenthash].[ext]' }
時,(static是我的靜態(tài)資源目錄)衅胀,最后打包圖片存放的路徑將會是dist/static/img/logo.da7ef7de.png
publicPath 定義目標文件的公共訪問路徑(前綴)岔乔,即項目運行時能正確引用資源的路徑前綴
默認值為webpackConfig.output.publicPath
+file-loader.outputPath
,
最終引用的文件路徑則為webpackConfig.output.publicPath
+file-loader.outputPath
+file-loader.name
滚躯。項目運行時我們訪問的index.html的根目錄就是 dist雏门,因此訪問路徑會像這樣:/static/img/logo.da7ef7de.png
嘿歌。-
name:打包后輸出的文件名
我們可以把 outputPath 的內容直接寫到name中,即在前面加上存放目錄茁影,一樣可以生成需要的輸出路徑宙帝。這也是vue-cli4默認的做法。如:.loader('file-loader') .options({ name: '[name].[contenthash].[ext]', outputPath: 'static/img' })
簡化成
.loader('file-loader') .options({ name: 'static/img/[name].[contenthash].[ext]' })
從生成的資源覆寫 filename 或 chunkFilename 時募闲,
vue.config.js
配置的assetsDir
會被忽略步脓。
因此別忘了在前面加上靜態(tài)資源目錄,即assetsDir指定的目錄蝇更,不然會直接在dist文件夾下,配置 outputPath 時同理呼盆。
用函數(shù)作為outputPath/publicPath選項的值
我們可以通過配置 publicPath 項使資源引用 URL 為相對路徑年扩,簡易版:
.loader('file-loader')
.options: {
name: '[name].[contenthash].[ext]',
outputPath: 'static/img',
publicPath: '../static/img'
// publicPath: 'static/img' // 也可
}
另外,如要給不同資源分別定義存儲目錄/訪問路徑前綴访圃,可以用函數(shù)來配置。outputPath/publicPath 的回調參數(shù)如下:
-
url
是 options.name 選項的值腿时,如'[name].[contenthash].[ext]'
况脆,必須配置在路徑最后批糟。需要注意當自定義配置了 outputPath 就不要在 options.name 里再加上目錄了盛末,name 只負責文件名就好,不然這樣得到的 url 參數(shù)有了目錄前綴檐嚣,就不太方便再處理。 -
resourcePath
是資源打包前的原始絕對路徑 -
context
是資源文件的根部上下文(rootContext)隐解,也就是項目根目錄的絕對路徑挖藏,或你自定義的 context 配置項
可以這樣獲取項目根目錄到資源的相對路徑:
const relativePath = path.relative(context, resourcePath)
;
path.relative(from, to) 方法:返回從
from
到to
的相對路徑
舉例:
path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb');
,會返回:'../../impl/bbb'
path.relative('/', '/src/assets/bg_images/main-bg.jpg');
厢漩,會返回'src/assets/bg_images/main-bg.jpg'
// outputPath 和 publicPath 配置演示膜眠,非vue-cli4的處理:
.options({
name: '[name].[contenthash].[ext]',
// outputPath: 'static/img', // 別忘了加上靜態(tài)資源目錄這個前綴,即assetsDir指定的目錄,不然會直接在dist文件夾下
outputPath: function (url, resourcePath, context) {
// 返回從項目根目錄到該圖片的相對路徑
const relativePath = path.relative(context, resourcePath)
const pathArr = relativePath.split('/')
// 如果你的靜態(tài)資源目錄結構較為簡單(最多二個層級)宵膨,圖片只放在/src/assets/ 或/src/assets/xxx
// 希望根據(jù)assets下的目錄結構原樣輸出架谎,可以這樣做
if (pathArr[3] !== undefined) {
return `static/img/${pathArr[2]}/${url}` // url 是上面配置的 name 的值,必須加在路徑最后
}
return `static/img/${url}`
// 這些都可依照個人習慣來安排辟躏,個人建議沒必要太復雜
// if (/denglun-bg\.jpg/.test(resourcePath)) {
// 如果圖片以 denglun-bg.jpg 結尾
// return `static/denglun/${url}`
// }
// if (/bg_images\//.test(resourcePath)) {
// 如果圖片路徑包含 bg_images 目錄
// return `static/bg_images/${url}`
// }
// return `static/img/${url}`
},
publicPath: (url, resourcePath, context) => {
// 如果要讓資源引用地址輸出為相對路徑谷扣,把 `outputPath` 的內容拷貝一份到這里即可
}
},
這種情況如果需要指定資源引用URL為相對路徑,也需用函數(shù)配置 publicPath捎琐,照著 outputPath
的內容依葫蘆畫瓢即可会涎。
先看看vue-cli4生產(chǎn)環(huán)境打包的默認處理結果:
我們打包前的靜態(tài)資源目錄(assets
)的結構如下(只展示圖片路徑相關資源):
src
├─ assets
│ ├─ bg_images
│ │ └─ denglun-bg.jpg
│ ├─ denglun-bg.jpg
│ ├─ denglun-limit.jpg
│ └─ denglun.jpg
├─ styles
│ └─ index.scss
└─ views
└─ dashboard
└─ index.vue
打包目錄的結構(同樣只是做個演示):
dist
├─ index.html
└─ static
├─ css
│ ├─ app.02c07ea3.css
│ ├─ chunk-01c6456a.1d1f9d97.css
│ ├─ chunk-components.4732cb5c.css
│ └─ chunk-libs.a1d59a71.css
├─ img
│ ├─ denglun-bg.ba926c29.jpg
│ ├─ denglun-limit.433994b0.jpg
│ └─ denglun.901f400f.jpg
└─ js
├─ app.60f57078.js
└─ chunk-libs.8726a2b2.js
原先打包自認為是小圖片卻沒轉成內聯(lián)base64?原來是其實我的圖片全部都大于默認的limit值(也就是4096/4kb瑞凑,不夠仔細)末秃,改成10240后效果才出來:
測試環(huán)境打包后,css中的資源URL默認是/static/img/xxx.h86a0sh3.jpg
這樣的絕對路徑∽延現(xiàn)在我們來看看生產(chǎn)環(huán)境輸出的*.css
文件中引用的資源相對路徑是怎么處理的练慕。
用 mini-css-extract-plugin 打包 css 時資源URL路徑配置
這件事 vue-cli4 通過 mini-css-extract-plugin@0.9.0 已經(jīng)幫我們完美處理好了~ ??注意,這個插件最新版本把
publicPath
屬性放到loader
下了技掏,chainWebpack 鏈式配置時要放到 loaderOptions 里铃将。?? 如果手動安裝配置mini-css-extract-plugin
的話要留意區(qū)分。
打包后css文件的輸出路徑是dist/靜態(tài)資源目錄/css/name.[contenthash:8].css
哑梳,如:dist/static/css/app.e3db5d0a.css
劲阎,源碼:
// ./node_modules/@vue/cli-service/lib/config/css.js
const filename = getAssetPath(
rootOptions,
`css/[name]${rootOptions.filenameHashing ? '.[contenthash:8]' : ''}.css`
)
由此css文件的訪問絕對路徑是/靜態(tài)資源目錄/css/文件名.css
,如:/static/css/app.e3db5d0a.css
生產(chǎn)環(huán)境模式鸠真,vue-cli 4 做了如下配置:
// ./node_modules/@vue/cli-service/lib/config/css.js
module.exports = (api, rootOptions) => {
api.chainWebpack(webpackConfig => {
// use relative publicPath in extracted CSS based on extract location
// 設置 publicPath 為輸出的 css 文件基于項目打包根目錄的相對路徑
const cssPublicPath = process.env.VUE_CLI_BUILD_TARGET === 'lib'
// 在 lib 模式下, CSS 會被提取到根目錄下
? './'
: '../'.repeat( // 將filename路徑最前面的 './' '.\'先去掉哪工,如果是'/'(絕對路徑)就原樣輸出,再根據(jù) (/ 或 \ 的 數(shù)量) -1弧哎,確定重復 '../' 的從次數(shù)
extractOptions.filename // 這個filename就是css文件的輸出文件名
.replace(/^\.[\/\\]/, '')
.split(/[\/\\]/g)
.length - 1
)
function createCSSRule (lang, test, loader, options) {
// ... 省略了大段代碼雁比,主要截取配置publicPath部分,具體可以去源碼
function applyLoaders (rule, isCssModule) {
if (shouldExtract) { // 若shouldExtract為true撤嫩,表示生產(chǎn)環(huán)境且非shadowMode
rule
.use('extract-css-loader')
.loader(require('mini-css-extract-plugin').loader)
.options({
hmr: !isProd,
publicPath: cssPublicPath // 默認值是 webpack 配置的 output.publicPath
})
}
// ...省略
}
}
}
}
mini-css-extract-plugin
的作用是為每個包含css的js文件創(chuàng)建一個單獨的.css
文件偎捎。
我們重點關注一下它 Loader 選項的 publicPath :<String|Function>
為 css 內引入的圖片、文件等外部資源指定一個URL公共路徑序攘。
默認值為vue.config.js
的 publicPath (也就是 webpack 的 output.publicPath茴她,一般是'/
')。css中引入的URL最終會處理成這個publicPath
+ 該資源文件的訪問路徑程奠。
通過分析代碼中的變量 cssPublicPath 得出丈牢,它的值即為打包后的 css 文件基于 dist 的相對路徑。用這個前綴再加上資源的訪問路徑即可高枕無憂了(never goes wrong)瞄沙。最終就是像url(../../static/img/denglun-bg.4baebe12.jpg)
看下 mini-css-extract-plugin 用函數(shù)配置 publicPath 的例子己沛,也是一樣的效果:
options: {
publicPath: (resourcePath, context) => {
// publicPath 是css文件相對于上下文(項目根目錄)的相對路徑
// path.dirname(resourcePath) 慌核,返回resourcePath的目錄
return path.relative(path.dirname(resourcePath), context) + '/';
},
},
其中:path.dirname(path) 的返回值是當前路徑的上層目錄(絕對路徑)
例如:/css/main.css
所在目錄為/css
,那么相對于項目根目錄的路徑就是 ../
申尼;
而 /static/css/index.css
所在目錄是/static/css
垮卓,publicPath 就會是 ../../
當我們用 image-webpack-loader 壓縮圖片后,size會小很多师幕,基本都會被轉成base64 URI內聯(lián)在css和js中粟按。后面這些就不用 mini-css-extract-plugin 處理css中資源的相對路徑了。反正目標是靜態(tài)資源小一點再小一點霹粥,需要 file-loader 輸出的資源越少越好灭将。
and file-loader 和 url-loader 在 webpack5 就棄用了??,被資源模塊(asset module)取代以后不用配置 loader 了后控,可以去了解下庙曙。
最后(也是寫給自己):碰到不清楚的地方,一定要多看官方文檔多分析源碼(細讀官網(wǎng)避免亂百度的彎路忆蚀,碰到不懂的就去看源碼理解透徹)矾利,靠自己厘清實現(xiàn)原理姑裂。不要框架幫忙整合好了就不管不顧了馋袜。不然永遠只是照搬別人的配置,版本升級或者換個插件就不會了舶斧,毫無收獲欣鳖。
今后努力方向:精煉明了,杜絕長篇大論茴厉。