既是單頁面, 又是服務端渲染html
正好幫我刷一下訪問量... http://respo.site/
注意站點的特別之處, 絕大部分的連接都是觸發頁面局部渲染的,
而在特殊的 js 沒法加載的狀況下, 仍是以連接的形式刷新頁面.
因此, 整個站點既是單頁面, 又是服務端渲染... 感覺一下.前端
文檔站是基於 React 寫的, 用的是本身的 router 和 store 方案
源碼看 https://github.com/Respo/resp...
爲了作頁面局部渲染, 前端運行 React 是必要的,
特殊的無 js 的場景, 對應的主要是爬蟲, 方便搜索引擎抓取.
並且爲了搜索引擎抓取, 中間的連接須要準確無誤,vue
好比說我瀏覽頁面, 在前端渲染好了 tutorial.html
這個頁面
http://respo.site/guide/tutor...
這個頁面是從側邊欄的連接點擊, 而後修改 url 而且渲染頁面的,
那麼, 服務器上要保證有對應的 tutorial.html
文件提供內容,
文檔實際上有幾十個頁面, 因此幾十個 HTML 都是預先渲染好的.
只不過用戶訪問時, 從任何一個地址進入, 後續的點擊都是局部渲染的.react
Gulp 渲染和前端渲染應該是一致的git
這個方案有幾個特徵:github
使用默認 Nginx 便可, 不須要額外的服務器腳本, 更快vue-router
訪問首屏幾乎就是服務端渲染, 內容展現更快shell
訪問後續頁面都是客戶端渲染, 更快gulp
除了首屏加載的 js 代碼沒有作分塊以外, 整體感受都比較好,
沒有等待服務端渲染的時間, 也沒有等待客戶端加載 js 再渲染的時間,
初看這是一個比較理想的站點的優化方案.後端
固然, 這個頁面比較簡單, 沒有經過 Ajax 加載 JSON 再渲染的部分,
對於服務端渲染方案來講, 一個作法是由服務器抓取這部分 JSON 而後渲染,
可是這會消耗兩塊時間, 1) 抓取數據時間, 2) 渲染頁面的時間,
略去動態內容的話, 靜態的頁面徹底能夠在 Gulp 建構時渲染完成,
單單考慮這種狀況, 我以爲用 Gulp 預渲染頁面是比較切實可行的方案.
至於動態內容, 我以爲在客戶端經過腳本抓取是更簡單的方案,
除了 SEO 之類的場景當中會要求有動態內容, 通常都能知足須要.
Model, View 以及路由問題
大體是這樣一個過程:
將整個頁面看作成是一個 Component, 根據路由渲染對應內容
在瀏覽器當中, 路由經過地址欄獲取, 而後渲染 Component
在 Gulp 環境, 路由經過字符串模擬, 渲染渲染頁面爲 HTML
建構過程中, 窮舉全部路由和對應的文件名, 存儲 HTML 文件
首先就是服務端很容易遇到的 Component 的加載和渲染問題,
須要處理一些瀏覽器依賴在 Node 環境中報錯的問題,
其次是組件經過 ReactDOM API 所有渲染成爲 HTML.
這部分在 React 和 Webpack 社區當中已經相對成熟.
這個步驟對路由模塊有作了一些要求, 須要能在服務端運行,
原理上說, 服務端解析路由歷史悠久, 方案並不存在問題,
麻煩的地方是要把前端路由模塊拿到後端, 一致地運行,
並且, 最好是很容易用 Node 腳本直接去操做, 以便處理多個頁面,
解耦作得好的話, 都還不錯. 可是也要看具體的路由支持怎樣.
一個很小衆的路由模塊
https://github.com/react-chin...
在 router-as-view
當中提供了 parseAddress
函數,
能夠根據規則直接將路由 /a/b?c=d
轉換爲一份數據,
隨後被放進 Store 當中, 而後 Component 直接依據 Store 進行渲染.
{parseAddress} = require 'router-as-view/lib/path' routes = Immutable.fromJS home: [] 'discuss.html': [] guide: ['entry'] docs: ['post'] router = parseAddress path, routes initialStore = schema.store.set 'router', router ReactDOM.renderToString(Container(store: store}))
從而很容易進行抽象, 而後再 Gulp 裏調用渲染 HTML:
gulp.task 'entries', (cb) -> // ... pages.forEach (pathname) -> // ... fs.writeFileSync "build/#{filename}.html", html(env, routerPath) // ...
好久沒用了...
具體實現不清楚, 我只能摘錄文檔當中的例子:
https://github.com/ReactTrain...
import { renderToString } from 'react-dom/server' import { match, RoutingContext } from 'react-router' import routes from './routes' serve((req, res) => { // Note that req.url here should be the full URL path from // the original request, including the query string. match({ routes, location: req.url }, (error, redirectLocation, renderProps) => { if (error) { res.status(500).send(error.message) } else if (redirectLocation) { res.redirect(302, redirectLocation.pathname + redirectLocation.search) } else if (renderProps) { res.status(200).send(renderToString(<RoutingContext {...renderProps} />)) } else { res.status(404).send('Not found') } }) })
Vue 2 文檔上沒解釋服務端路由的問題, 從已經發布的 Demo 看是支持的,
並且像是爲了服務端渲染順帶就支持了...
沒有文檔很差說, 只能看下代碼當中的例子來推測一下:
https://github.com/vuejs/vue-...
https://github.com/vuejs/vue-...
respo-router 基本上是 router-as-view
的 cljs 版本, 細節還有待改進. 目前已經完成了初步的建構過程渲染的 Demo:
https://github.com/Respo/shel...
http://weibo.com/1651843872/E...
有點難看到懂, 可是大致的思路仍是用組件生成 HTML, 其餘部分都是腳本:
(defn html-dsl [data html-content ssr-stages router] (make-html (html {} (head {} (let [store (assoc schema/store :router router)] (script (:attrs {:id "store" :type "text/edn" :innerHTML (pr-str store)})))) (body {} (div {:attrs {:id "app" :innerHTML html-content}}) (script {:attrs {:src "/main.js"}}))))) (defn generate-html [router ssr-stages] (let [ tree (comp-container {:router router} ssr-stages) html-content (make-string tree)] (html-dsl {:build? true} html-content ssr-stages router))) (def dict {"post" ["post"], "about.html" [], "home" []}) (defn -main [] (spit "target/index.html" (generate-html (parse-address "/" dict) #{:shell})) (spit "target/about.html" (generate-html (parse-address "/about.html" dict) #{:shell})) (sh "mkdir" "target/post/") (spit "target/post/a.html" (generate-html (parse-address "/post/a.html" dict) #{:shell})) (spit "target/post/b.html" (generate-html (parse-address "/post/b.html" dict) #{:shell})))
其餘的方案, Angular 2 未知... 按說已經支持服務端渲染, 應該能搞定,
這只是一個套路, 跟具體的框架應該是無關的, 應該都能完成.
固然, 回到文章的核心觀點, 就是要提供一個 Gulp 渲染的方案,全部的頁面入口按照路由在服務端預渲染完成, 做爲一個優化,從而讓頁面在觀感上加載得更快.