先後端分離開發已成爲業界的共識,但分離的同時也帶來了部署的問題。傳統web模式下,前端和後端同屬一個項目,模板的渲染理所固然由後端渲染。然而隨着node的流行,以及webpack的模塊化打包方案,讓前端在開發階段徹底有能力脫離後端環境:經過本地node啓動一個服務器,搭配Mock數據,立刻就能夠進行業務開發了。javascript
可是到了部署階段,問題也就顯現出來:前端最後打包出來的js,css以及index.html,到底放在哪裏?靜態文件js,css或者圖片,咱們還能夠在CI階段上傳到cdn服務器上,可是最後的html模板index.html
必定須要放在一個服務器上,然而這個服務器到底由前端仍是後端維護?css
若是html模板由前端維護,那麼前端就須要本身使用一個靜態服務器:提供HTML
的渲染和API
接口的轉發。常見的單頁應用,也是推薦使用Nginx進行部署。html
使用Nginx部署,這裏又分兩種狀況:前端
dist
目錄裏。在這種狀況下,webpack的publicPath
通常不用特別設置,使用默認的/
便可。index.html
。在這種狀況下,webpack的publicPath
要設置成cdn的地址,例如://static.demo.cn/activity/share/
。但這又會引起一個問題,因爲qa環境,驗證環境,生產環境的cdn地址一般不一樣,爲了讓index.html
能夠引入正確的靜態文件路徑,你須要打包三次,僅僅爲了生成三份引用了不一樣路徑的html(即便三次打包的js內容徹底同樣)nginx配置
java
server {
listen 80;
server_name localhost;
location / {
root /app/dist; # 打包的路徑
index index.html index.htm;
try_files $uri $uri/ /index.html; # 單頁應用防止重刷新返回404,若是是多頁應用則無需這條命令
}
location /api {
proxy_pass https://anata.me; #後臺轉發地址
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
}
複製代碼
理論上qa,yz,prod環境的接口轉發地址也不一樣,所以你還須要三份nginx.conf
配置node
不少狀況下,咱們須要渲染的頁面帶上後端注入的動態數據,又或者頁面須要支持SEO,這種狀況下,咱們只能把模板交給後端渲染。那麼後端維護的html模板怎麼獲取打包後的hash值呢?webpack
index.html
直接發給後端(簡單粗暴,並不推薦)webpack-manifest-plugin
後生成一個manifest.json
文件,該文件實際上是個key-value的鍵值對,key表明了資源名稱,value記錄了資源的hash{
"common.css": "/css/common/common-bundle.804a717f.css",
"common.js": "/js/common/common-bundle.fcb76db9.js",
"manifest.js": "/js/manifest/manifest-bundle.551ff423.js",
"vendor.js": "/js/vendor/vendor-bundle.d99dc0e4.js",
"page1.css": "/css/demo/demo-bundle.795bbee4.css",
"page1.js": "/js/demo/demo-bundle.e382801f.js",
}
複製代碼
後端的index.html
nginx
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>demo</title>
<link href="<%= manifest['page1.css']%>" rel="stylesheet">
</head>
<body>
<h1>demo</h1>
<script src="<%= manifest['page1.js'] %>"></script>
</body>
</html>
複製代碼
後端經過讀取這個json
文件,就能夠動態渲染出文件的引用路徑。git
若是你曾經用過百度的打包工具FIS,它最後打包產出的map.json就是相似的資源文件列表。github
使用這種方法還有一個好處:前面咱們說過,若是文件上傳至cdn,那麼前端維護的html可能須要打包三次,由於不一樣環境的cdn地址不一樣。如今html交給後端維護了,那麼這個問題就很好解決,前端只須要打包一次,不一樣環境的cdn地址可讓後端動態拼接生成。
固然,使用這種方法也會帶來一個問題,這個json文件,後端怎麼獲取?
wget --tries=3 --quiet -O manifest.json http://static.demo.cn/demo/manifest.json?`date +%s` ## 防止緩存
複製代碼
方案的優勢:簡單方便,每次前端打包,manifest.json
就會自動更新,上傳到cdn同時覆蓋前一個版本。 方案的缺點:若是manifest.json
更新了,後端則須要重啓服務以便獲取新的配置,當集羣多的時候,重啓耗費的代價可能很大。
manifest.json
的內容放在配置中心裏,後端則須要接入配置中心。每次CI打包後,調用配置中心更新接口,後端就能自動獲取最新的配置。在我平時工做項目中,這兩種方案均有實現。
使用Nginx部署時,爲了解決跨域問題,咱們通常須要配置proxy_pass
指向提供api的後端服務。
後端採用了SOA,微服務的架構時,proxy_pass
指向的api服務器,其實本質也是一個轉發服務。
前端ajax請求
// 獲取商品列表
ajax.get('/api/queryProductList')
// 獲取價格列表
ajax.get('/api/queryPriceList')
複製代碼
Nginx轉發
location /api {
proxy_pass https://demo.com; #後臺轉發地址
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
複製代碼
接口轉發到
https://demo.com/api/queryProductList
https://demo.com/api/queryPriceList
查詢商品列表和查詢價格列表實際上是由兩個不一樣的soa服務提供:
查詢商品: product.soa.neko.com
查詢價格: price.soa.neko.com
所以,本質上https://demo.com
這個服務也只是用來轉發接口,同時對數據作部分的組裝。那麼這個服務,就能夠用Node中間層來替代。使用了Node中間層,模板的渲染也能夠從Nginx轉移到Node了。
固然,多了一層Node,對於前端的綜合要求也隨之提升,後端的部署,監控,日誌,性能等等問題也隨之而來,全棧(幹)工程師應運而生。
我司大部分to C的前端項目,都是採用Node層渲染模板加轉發接口的開發模式,還有少許項目採用Java tomcat渲染html模板。
大部分頁面都是多頁應用,並非典型的單頁應用。
Node層渲染模板,又分兩種狀況:
固然近幾年比較流行的SSR方案,讓Node渲染模板時能夠直接使用Vue和React的同構組件,直出頁面後,用戶的交互體驗又如單頁應用般流暢,只能說:歷史老是驚人的類似。
從某種程度上說,SSR是一種向傳統模式的迴歸,不過這種迴歸並非倒退,而是一種螺旋式的發展。
理論知識講了那麼多,如今咱們來實戰一下。在上一篇文章裏,我介紹了webpack多頁打包的原理,同時搭建了一個簡單的webpack4-boilerplate。這個模板只是一個前端開發模板,其實它還對應着一個node後端模板koa2-multipage-boilerplate。
這個node項目最重要的就是實現了前面說的:如何讀取manifest.json
文件,動態渲染靜態文件的引用路徑,從而先後端分離開發和部署。
詳情見chunkmap.js這個koa2中間件的源碼。
const chunkmap = require('./chunkmap');
app.use(chunkmap({
staticServer: '//0.0.0.0:9001',
staticResourceMappingPath: './mainfest.json'
}));
複製代碼
這個中間件接受兩個參數
webpack4-boilerplate
這個前端項目啓動的服務器。到了qa,產線時,則填寫真正的cdn地址manifest.json
文件本地開發時的manifest.json
,不帶hash值
{
"home.css": "/css/home/home-bundle.css",
"home.js": "/js/home/home-bundle.js",
}
複製代碼
打包後的manifest.json
,帶hash值
{
"home.css": "/css/home/home-bundle.d2378378.css",
"home.js": "/js/home/home-bundle.cb49dfaf.js",
}
複製代碼
使用了這個中間件後,koa的ctx.state
全局變量上就帶有一個bundle
屬性,裏面的內容是:
{
"home.css": "//0.0.0.0:9001/css/home/home-bundle.d2378378.css",
"home.js": "//0.0.0.0:9001/js/home/home-bundle.cb49dfaf.js",
}
複製代碼
而後經過模板引擎,動態渲染出實際頁面。固然你也能夠在頁面中動態生成展現內容,從而支持SEO。
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<title><%= title %></title>
<link href="<%= bundle['home.css']%>" rel="stylesheet">
</head>
<body>
<div id="app"></div>
<script src="<%= bundle['home.js']%>"></script>
</body>
</html>
複製代碼
先後端分離帶來了工做效率上的提升,Node中間層則給前端打開了一條走進後端的道路。固然機遇老是與挑戰並存,在前端技術突飛猛進的今天,真想說一句:老子學不動了!