摘要:研究下,地址,當然它也可以作為的中間件使用中介紹的很清楚解釋一下首頁比如我使用訪問,發(fā)現(xiàn)顯示,點擊,顯示,地址欄變?yōu)?,一切正常,這時候刷新當前頁面或或點擊瀏覽器的刷新按鈕或在地址欄上再敲一下回車,發(fā)現(xiàn)了哦文檔
研究下connect-history-api-fallback v1.3.0,地址:https://github.com/bripkens/c...,當然它也可以作為express的中間件使用
README中介紹的很清楚
解釋一下:Single Page Applications (SPA) typically only utilise one index file that is accessible by web browsers: usually index.html. Navigation in the application is then commonly handled using JavaScript with the help of the HTML5 History API. This results in issues when the user hits the refresh button or is directly accessing a page other than the landing page, e.g. /help or /help/online as the web server bypasses the index file to locate the file at this location. As your application is a SPA, the web server will fail trying to retrieve the file and return a 404 - Not Found message to the user.
This tiny middleware addresses some of the issues. Specifically, it will change the requested location to the index you specify (default being /index.html) whenever there is a request which fulfills the following criteria:
The request is a GET request
which accepts text/html,
is not a direct file request, i.e. the requested path does not contain a . (DOT) character and
does not match a pattern provided in options.rewrites (see options below)
server.js
const path = require("path") const express = require("express") const router = express.Router() const indexRoute = router.get("/", (req, res) => { res.status(200).render("index", { title: "首頁" }) }) app.set("views", path.join(__dirname, "templates")) app.set("view engine", "html") app.engine("html", ejs.__express) app.use("/static", express.static(path.join(__dirname, "public"))) app.use(history({ rewrites: [ { from: /^/abc$/, to: "/" } ] })) app.get("/", indexRoute) app.use((req, res) => { res.status(404).send("File not found!") }) app.listen(9090, "127.0.0.1", () => { console.log("ther server is running at port " + 9090) })
index.html
index.js
Vue.use(VueRouter) var s = "" var Home = { template: "Go to Foo -Go to Bar -Go to Home " + s + "", created: function() { console.log("home") } } var Foo = { template: "home" + "" + s + "", created: function() { console.log("foo") }} var Bar = { template: "foo" + "" + s + "", created: function() { console.log("bar") }} var NotFoundComponent = { template: "bar" + "" + s + "", created: function() { console.log("not found") }} var routes = [ { path: "/", component: Home }, { path: "/foo", component: Foo }, { path: "/bar", component: Bar }, { path: "*", component: NotFoundComponent } ] var router = new VueRouter({ mode: "history", routes: routes }) new Vue({ router: router }).$mount("#test")not found" + "
比如我使用vue-router, 訪問http://localhost:9090,發(fā)現(xiàn)顯示home,點擊Go to Foo,顯示foo,地址欄變?yōu)閔ttp://localhost:9090/foo,一切正常,ok
這時候刷新當前頁面(ctrl+R或ctrl+command+R或點擊瀏覽器的刷新按鈕或在地址欄上再敲一下回車),發(fā)現(xiàn)404了哦
vue-router文檔針對這種情況做了很好的解釋:
Not to worry: To fix the issue, all you need to do is add a simple catch-all fallback route to your server. If the URL doesn"t match any static assets, it should serve the same index.html page that your app lives in. Beautiful, again!
如果express server使用了connect-history-api-fallback middleware,在你定義router的前面app.use(history({ rewrites: [ { from: /^/abc$/, to: "/" } ] }))一下
再刷新頁面,發(fā)現(xiàn)地址仍然http://localhost:9090/foo,然而走進了咱們的前端路由,chrome控制臺顯示了foo,真的是beautiful again
其實過程也很簡單啦,請求/foo,走到了咱們的history-api-fallback中間件,然后他看你沒有rewrite,那么好,我把req.url改成"/",于是vue-router發(fā)現(xiàn)地址/foo,所以根據(jù)routes的map,渲染了Foo組件
但是萬一有人輸入地址/abc,怎么辦? vue-router定義了{ path: "*", component: NotFoundComponent }用來catch-all route within your Vue app to show a 404 page
Alternatively, if you are using a Node.js server, you can implement the fallback by using the router on the server side to match the incoming URL and respond with 404 if no route is matched.
地址輸入/abc,回車,走到vue-router,會顯示not found
地址輸入/xyz,回車,走到服務(wù)端路由,http狀態(tài)為404,然后顯示File not found!
source code 分析貼下代碼
"use strict"; var url = require("url"); exports = module.exports = function historyApiFallback(options) { options = options || {}; var logger = getLogger(options); return function(req, res, next) { var headers = req.headers; if (req.method !== "GET") { logger( "Not rewriting", req.method, req.url, "because the method is not GET." ); return next(); } else if (!headers || typeof headers.accept !== "string") { logger( "Not rewriting", req.method, req.url, "because the client did not send an HTTP accept header." ); return next(); } else if (headers.accept.indexOf("application/json") === 0) { logger( "Not rewriting", req.method, req.url, "because the client prefers JSON." ); return next(); } else if (!acceptsHtml(headers.accept, options)) { logger( "Not rewriting", req.method, req.url, "because the client does not accept HTML." ); return next(); } var parsedUrl = url.parse(req.url); var rewriteTarget; options.rewrites = options.rewrites || []; for (var i = 0; i < options.rewrites.length; i++) { var rewrite = options.rewrites[i]; var match = parsedUrl.pathname.match(rewrite.from); if (match !== null) { rewriteTarget = evaluateRewriteRule(parsedUrl, match, rewrite.to); logger("Rewriting", req.method, req.url, "to", rewriteTarget); req.url = rewriteTarget; return next(); } } if (parsedUrl.pathname.indexOf(".") !== -1 && options.disableDotRule !== true) { logger( "Not rewriting", req.method, req.url, "because the path includes a dot (.) character." ); return next(); } rewriteTarget = options.index || "/index.html"; logger("Rewriting", req.method, req.url, "to", rewriteTarget); req.url = rewriteTarget; next(); }; }; function evaluateRewriteRule(parsedUrl, match, rule) { if (typeof rule === "string") { return rule; } else if (typeof rule !== "function") { throw new Error("Rewrite rule can only be of type string of function."); } return rule({ parsedUrl: parsedUrl, match: match }); } function acceptsHtml(header, options) { options.htmlAcceptHeaders = options.htmlAcceptHeaders || ["text/html", "*/*"]; for (var i = 0; i < options.htmlAcceptHeaders.length; i++) { if (header.indexOf(options.htmlAcceptHeaders[i]) !== -1) { return true; } } return false; } function getLogger(options) { if (options && options.logger) { return options.logger; } else if (options && options.verbose) { return console.log.bind(console); } return function(){}; }
getLogger, 默認不輸出,options.verbose為true,則默認console.log.bind(console),但不知道這里bind意義何在 - -,也可以直接傳logger,比如debug
如果req.method != "GET",灰,結(jié)束
如果!headers || !headers.accept != "string" (有這情況?),灰,結(jié)束
如果headers.accept.indexOf("application/json") === 0,灰,結(jié)束
acceptsHtml函數(shù)a判斷headers.accept字符串是否含有["text/html", "/"]中任意一個
當然不夠這兩個不夠你可以自定義到選項options.htmlAcceptHeaders中
!acceptsHtml(headers.accept, options),灰,結(jié)束
然后根據(jù)你定義的選項rewrites(沒定義就相當于跳過了)
按定義的數(shù)組順序,字符串依次匹配路由rewrite.from,匹配成功則走rewrite.to,to可以是字符串也可以是函數(shù),綠,結(jié)束
判斷dot file,即pathname中包含.(點),并且選項disableDotRule !== true,即沒有關(guān)閉點文件限制規(guī)則,灰,結(jié)束
那么剩下的情況(parsedUrl.pathname不含點,或者含點但關(guān)閉了點文件規(guī)則)
rewriteTarget = options.index || "/index.html",綠結(jié)束
稍微注意下,他是先匹配自定義rewrites規(guī)則,再匹配點文件規(guī)則
測試部分用的是nodeunit,具體用法https://github.com/caolan/nod...
隨便看兩個測試用例
var sinon = require("sinon"); var historyApiFallback = require("../lib"); var tests = module.exports = {}; var middleware; var req = null; var requestedUrl; var next; tests.setUp = function(done) { middleware = historyApiFallback(); requestedUrl = "/foo"; req = { method: "GET", url: requestedUrl, headers: { accept: "text/html, */*" } }; next = sinon.stub(); done(); }; // .... tests["should ignore requests that do not accept html"] = function(test) { req.headers.accept = "application/json"; // 調(diào)用middleware middleware(req, null, next); // 測試req.url是否等于requestedUrl test.equal(req.url, requestedUrl); // next是否被調(diào)用過 test.ok(next.called); // 測試結(jié)束 test.done(); }; // ... tests["should rewrite requests when the . rule is disabled"] = function(test) { req.url = "js/app.js"; middleware = historyApiFallback({ disableDotRule: true }); middleware(req, null, next); // 測試req.url是否等于/index.html // 因為pathname中有點,且關(guān)閉了點規(guī)則 // req.url應(yīng)該被rewrit成了/index.html test.equal(req.url, "/index.html"); test.ok(next.called); test.done(); }; // ... tests["should test rewrite rules"] = function(test) { req.url = "/socer"; middleware = historyApiFallback({ rewrites: [ {from: //soccer/, to: "/soccer.html"} ] }); middleware(req, null, next); // 因為沒有匹配上rewrites里的規(guī)則 // 而req.url pathname又不含點 // 所以req.url 倒退到了index.html test.equal(req.url, "/index.html"); test.ok(next.called); test.done(); };
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/86595.html
摘要:前言由于博主最近又閑下來了,之前覺得的官方文檔比較難啃一直放到現(xiàn)在。文章會逐步分析每個處理的用意當然是博主自己的理解,不足之處歡迎指出溝通交流。后續(xù)將會補上構(gòu)建生產(chǎn)的配置分析,案例參考。前端臨床手札構(gòu)建逐步解構(gòu)下 前言 由于博主最近又閑下來了,之前覺得webpack的官方文檔比較難啃一直放到現(xiàn)在。細心閱讀多個webpack配置案例后覺得還是得自己寫個手腳架,當然這個案例是基于vue的,...
摘要:是目前使用最為火熱的打包工具,各大知名的框架類庫都用其打包,國內(nèi)使用最近也火熱起來。但是坑也很多,比如說圖片,字體等文件的路徑。 webpack 是目前使用最為火熱的打包工具,各大知名的框架類庫都用其打包,國內(nèi)使用最近也火熱起來。它在單頁應(yīng)用和類庫打包上幫助許多人從代碼管理中解脫了出來,成為了當下風(fēng)靡一時的打包工具。 但是坑也很多,比如說圖片,字體等文件的路徑。 剛開始用webpack...
摘要:項目結(jié)構(gòu)為我們搭建開發(fā)所需要的環(huán)境目錄結(jié)構(gòu)及主要功能項目構(gòu)建相關(guān)代碼生產(chǎn)環(huán)境構(gòu)建代碼檢查等版本熱重載相關(guān)構(gòu)建本地服務(wù)器構(gòu)建工具相關(guān)基礎(chǔ)配置開發(fā)環(huán)境配置生產(chǎn)環(huán)境配置項目開發(fā)環(huán)境配置開發(fā)環(huán)境 Vue-cli 項目結(jié)構(gòu) vue-cli 為我們搭建開發(fā)所需要的環(huán)境 目錄結(jié)構(gòu)及主要功能 |-- build // 項目構(gòu)建(webpack)...
摘要:項目結(jié)構(gòu)為我們搭建開發(fā)所需要的環(huán)境目錄結(jié)構(gòu)及主要功能項目構(gòu)建相關(guān)代碼生產(chǎn)環(huán)境構(gòu)建代碼檢查等版本熱重載相關(guān)構(gòu)建本地服務(wù)器構(gòu)建工具相關(guān)基礎(chǔ)配置開發(fā)環(huán)境配置生產(chǎn)環(huán)境配置項目開發(fā)環(huán)境配置開發(fā)環(huán)境 Vue-cli 項目結(jié)構(gòu) vue-cli 為我們搭建開發(fā)所需要的環(huán)境 目錄結(jié)構(gòu)及主要功能 |-- build // 項目構(gòu)建(webpack)...
閱讀 2611·2021-10-14 09:43
閱讀 3570·2021-10-13 09:39
閱讀 3304·2019-08-30 15:44
閱讀 3154·2019-08-29 16:37
閱讀 3718·2019-08-29 13:17
閱讀 2742·2019-08-26 13:57
閱讀 1834·2019-08-26 11:59
閱讀 1260·2019-08-26 11:46