webpack 是目前使用最爲火熱的打包工具,各大知名的框架類庫都用其打包,國內使用最近也火熱起來。它在單頁應用和類庫打包上幫助許多人從代碼管理中解脫了出來,成爲了當下風靡一時的打包工具。css
可是坑也不少,好比說圖片,字體等文件的路徑。html
剛開始用webpack的同窗很容易掉進圖片打包這個坑裏,好比打包出來的圖片地址不對或者有的圖片並不能打包進咱們的目標文件夾裏。明明在開發階段都是好好的,一但發佈到線上,就出現各類404,vue
通常來講,webpack打包的SPA程序,發佈到網站的根目錄下都不會出現太多問題,可是發佈到網站的子目下,就會出現各類資源文件找不到的狀況。node
這種文件,我在如下這個知乎的帖子裏曾經作過詳細的回答webpack
知乎用戶:vuejs怎麼在服務器部署?web
彷佛全部問題都解決了,但有一個問題沒有解決,就是若是我在css裏引了了圖片資源,webpack並不能很好的處理這裏面的資源路徑vue-cli
好比我把spa部署在 https://www.wx2share.com/m/
「m」文件夾是網站下的一個子目錄,express
若是我在開發的時候,寫了以下css代碼api
.content { background: url('/static/img/1.jpg') }
首先,咱們修改(tips:個人配置文件是經過vue-cli生成的)瀏覽器
build: { env: require('./prod.env'), index: path.resolve(__dirname, '../dist/index.html'), assetsRoot: path.resolve(__dirname, '../dist'), assetsSubDirectory: 'static', assetsPublicPath: '/m/', //這裏指定publicPath 的路徑爲我子目錄文件名,通常默認爲'/' productionSourceMap: true, },
webpack publicPath 參數是用來,幫助你爲項目中的全部資源指定一個基礎路徑,一但設定值之後,全部代碼中經過requrie 或import 方式引入的資源文件,在build之後,都回指向相似 '/m/static/xxx.xx'
好比:
logo: require('@/assets/v.png') <img :src="logo">
編譯之後,
<div class="logo-warp"> <img src="/m/static/img/logo_small.f457a3f.png"> </div>
能夠看到,webpack 已經正確的把資源文件的路徑裏 加上了/m ,保證了資源文件引用正確,這裏順便提一點,publicpath 還能夠設置在cdn的url
若是 publicPath: 'https://cdn.youdomin.com/' 這樣編譯之後,src=''https://cdn.youdomin.com//static/img/logo_small.f457a3f.png
這樣你只要把static整個文件夾託管的你的cdn上就能夠了很是方便。可是publicPath 必定用絕對路徑,絕對路徑,絕對路徑(重要的事說3遍) 千萬不要用相對路徑,若是你把publicPath設爲'./',哪你啓用路由之後,https://www.wx2share.com/m/ 這樣的url進入程序的,不會有問題,由於這個時候,static文件正好在當前目錄下,可是當你相似用這樣的網址來訪問的時候 ,https://www.wx2share.com/m/sh... 資源文件又找不到了,由於這個時候./的指向的目錄是
/m/show/static/ 很明顯你的資源文件全在 /m/static下,因此又404了。
上面的方法基本解決了大部分的問題,惟一不能解決的就是文章開頭提到的,css中引入的資源文件的問題了
經過publicpath的設置,並不到改變css中引用資源文件的路徑,上面實例代碼中的css編譯後,仍是
.content { background: url('/static/img/1.jpg') }
是乎webpack 並不處理css中的文件路徑,這樣的結果就是發佈之後頁面上全部經過css引入的資源文件所有不能正常顯示了。前段時間我採用了一個簡單又粗爆的方法來解決這個問題,哪就是,絕對不用css引入任何文件。固然你也能夠手動修改webpack編譯過的文件,把經過查找替換 把/static 替換爲/m/static 若是你不嫌累的慌。
最近又開始了一個新的SPA應用,引入了一個第三方css,裏面好多經過css引入的圖片文件 ,讓我不得不下定決心來解決這個問題了
TIPS: 如下內容的配置文件修改所有是基於由vue-cli生成的配置文件,若是你是用本身寫的配置文件請注意區別!
我首先想到,若是開發的時候我就是指定在「m/」子目錄下,不就能解決大部分問題了,這樣和線上的根目錄文件對應了,buid之後就不會有哪麼多問題 ,
第一步:修改dev模式上的publicPath
dev: { env: require('./dev.env'), port: 8082, autoOpenBrowser: true, assetsSubDirectory: 'static', assetsPublicPath: '/m/', //把dev模式下的publicPath也設爲 m proxyTable: {}, cssSourceMap: false } yarn run dev
之後,訪問 http://localhost:8082/m/ 結果好麼,直接給我來了個 can not get /m/
可是若是我直接訪問http://localhost:8082/m/index.html 是能夠的,並且功能基本正常,是就vue的router不起做用了,在瀏覽器地址欄裏直接敲入 http://loalhost:8082/m/view/1 而後回車,這樣的地址就所有404了,看來是dev sever沒有把,全部連接重新定向到 /m/index.html上,可是publicPath設置是正確的了,如今要解決的問題就是,不管在瀏覽器地址欄裏輸入什麼網址,都讓它重定向到 /m/index.html 就能夠解決全部問題了,
webpack 的dev sever 是什麼,用什麼來實現的,能過查看buid/dev-server.js
發現是用express 來實現web server,經過加載webpack-dev-middleware 來實現實時編譯,全部請求都轉發給它了,因此,在node_modules下找到它的源代碼,看了老半天,看不出因此然來,只能debug了,看它究竟是怎麼運做的,電腦上沒有調試的工具,只好,經過打log的方法來調試了。
function webpackDevMiddleware(req, res, next) { function goNext() { if(!context.options.serverSideRender) return next(); return new Promise(function(resolve) { shared.ready(function() { res.locals.webpackStats = context.webpackStats; resolve(next()); }, req); }); } if(req.method !== "GET") { return goNext(); } var filename = getFilenameFromUrl(context.options.publicPath, context.compiler, req.url); console.log(filename) //關鍵就是這裏,只有filename不等於 false的時候才進入真正的處理階段 if(filename === false) return goNext(); //下面還有好多代碼,不粘貼了, 既然這裏返回 false
咱們進入 getFileNameFromUrl 這個函數看看,爲何會false
function getFilenameFromUrl(publicPath, outputPath, url) { var filename; console.log(publicPath, outputPath, url) //我在這裏打了個log // localPrefix is the folder our bundle should be in var localPrefix = urlParse(publicPath || "/", false, true); var urlObject = urlParse(url); // publicPath has the hostname that is not the same as request url's, should fail if(localPrefix.hostname !== null && urlObject.hostname !== null && localPrefix.hostname !== urlObject.hostname) { return false; } // publicPath is not in url, so it should fail if(publicPath && localPrefix.hostname === urlObject.hostname && url.indexOf(publicPath) !== 0) { return false; //就這裏return false了 } // strip localPrefix from the start of url if(urlObject.pathname.indexOf(localPrefix.pathname) === 0) { filename = urlObject.pathname.substr(localPrefix.pathname.length); } if(!urlObject.hostname && localPrefix.hostname && url.indexOf(localPrefix.path) !== 0) { return false; } // and if not match, use outputPath as filename return querystring.unescape(filename ? pathJoin(outputPath, filename) : outputPath); }
看上面代碼,我在進入函數的頭部打了一個log,看看傳入的參數究竟是什麼
當我訪問 http://localhost:8082/m/ 的時候 控制檯裏輸出
發現 publicPath, outputPath, url,三個參數的值分別爲:
/m/ F:workspacewx2share-pwadist /index.html
終於發現
if(publicPath && localPrefix.hostname === urlObject.hostname && url.indexOf(publicPath) !== 0) {
return false; //就這裏return false了 }
url.index.of(publicPath) !== 0 這一個條件成立了 至關於 '/index.html/'.indexOf('/m/') 確定不會===0 啊,
可是這個‘/index.html' 這個參數的值哪裏來的,回到上一段代碼中,發現是req.url裏傳過來的,
但我明明訪問的是/m/ 哪,req.url 應該等於 '/m/'啊,這是何時給重定向的呢,看來在這個以前,已經有過一次重定向了
回到dev-server.js文件,發如今,use devMiddlewarre 以前,還引入了一個connect-history-api-fallback的中間件,看來惟一能重定向的地方只有這裏了,
app.use(require('connect-history-api-fallback')()); // 服務器部署 webpack 打包的靜態資源 app.use(devMiddleware); // 使用熱更新, 若是編譯出現錯誤會實時展現編譯錯誤 app.use(hotMiddleware);
打開connect-history-api-fallback的代碼,終於發現了
rewriteTarget = options.index || '/index.html'; logger('Rewriting', req.method, req.url, 'to', rewriteTarget); req.url = rewriteTarget; next(); 這個的代碼,當你不做配置的時候,默認重定向到根目錄的/index.html上,找到緣由了,修改就簡單了 // 處理 history API 的回退狀況(若是在線上環境中,也須要服務器作相應處理) app.use(require('connect-history-api-fallback')({ index: '/m/index.html' }));
把options.index的值設爲 /m/index.html 不就能夠了吧,這樣全部的請求,都會轉發到/m/index.html了
再次打開http://localhost:8082/m/ 一切所有正常了,build之後,發如今線上,也徹底正常,不再會找不到css中引入的資源文件了
一個重點差點忘記提了
就是如今寫css代碼裏,引入static文件中的文件,直接要寫加上publicPath的路徑,
就是原來寫成
.content { background: url('/static/img/1.jpg') }
要在編碼階段所有寫成
.content { background: url('/m/static/img/1.jpg') //直接帶上發佈是的絕對路徑 }