緣起
時間真快,現在已經是這個系列教程的下半部 Vue 第 12 篇了,昨天我也簡單思考了下,可能明天再來一篇,Vue 就基本告一段落了,因為什么呢,這里給大家說個題外話,當時寫博文的時候,只是想給大家增加點兒學習的動力,每天提醒下,完全沒有提綱或者安排說明什么的,就是按照我自己學的方向走,正好發現了一個規律就是:每一個系列正好是 1 個引子 + 12 篇正文,不知道大家對這個有沒有感覺,大家可能看到我的頭像就知道了,哈哈,其實我是一個紅迷,正好這里機緣巧合,兩個系列都形成了這樣的,我自私的給自己畫了一個規划,正好是一組判詞——十二釵正冊,副冊,又副冊等等(說明:這是我自己的一廂情願哈,大家如果有紅迷愛好者,請不要噴我 [苦笑] ),按照計划,應該會寫 9 部,除了引子,正好是 108 篇,哈哈以后的再說吧。這里自己昨天瞎想了一通,如果有紅迷愛好者也可以找我喲,加群(867095512)或者個人QQ(3143422472)都行。
因為 Vue 這個系列還是很多的要說的,不過基本的咱們都說了,大家可以再通過傳送門看看《Vue 學習12篇》,今天呢,咱們就說說一個老生常談的問題,就是如何實現 Vue 的 SSR 服務端渲染,大家如果是第一次接觸到,可能還比較陌生,不要慌,本文就給大家通過 Nuxt.js 框架,來講解這個問題,然后為了以后實現咱們的第二個項目,大致會是這個樣子(注意頁面的源代碼已經有內容了):
注意:今天僅僅給大家說明 SSR 服務端渲染,會簡單說一個小 DEMO ,上圖中的框架具體的代碼,在咱們的第二個博客項目中會說到,因為這個框架一兩篇是說不完的。
零、今天要完成藍色區塊部分
一、Vue 的 SSR 到底是個什么?
來自官方的解釋:
Vue.js 是構建客戶端應用程序的框架。默認情況下,可以在瀏覽器中輸出 Vue 組件,進行生成 DOM 和操作 DOM。然而,也可以將同一個組件渲染為服務器端的 HTML 字符串,將它們直接發送到瀏覽器,最后將這些靜態標記"激活"為客戶端上完全可交互的應用程序。
服務端渲染的 Vue.js 應用程序也可以被認為是"同構"或"通用",因為應用程序的大部分代碼都可以在服務器和客戶端上運行。
羞澀難懂
我的個人理解就是:
1、目前Vue的模式是:
在生成頁面的工作中,我們現在是把組件放在瀏覽器里,然后把 Data 填充到組件中生成 DOM,這也是一般的異步操作的動作,咱們平時一定是這么操作的,先在頁面寫上 DIV,然后用 Jquery 獲取數據,把數據填充到 DIV 里
2、SSR的模式呢,轉變成了先在服務器中 Data 先把組件先渲染成 Html 字符串,當成靜態資源,就像 css 字符串那樣,再拋到前台頁面。
這第二種就是 SSR 服務端渲染,大家應該發現了這個和普通的區別——就是渲染html片段的控制權轉向了服務端,那為什么要這么做呢?請往下看。
注意:這里的服務端,並不是在我們的.net core api 中生成的,而是在vue中,我們通過webpack 打包后 node server來處理的。
二、為什么要 SSR 服務端渲染?
1、首先咱們需要說說搜索引擎 —— SEO
咱們打開任何搜索引擎,無論是谷歌,還是百度,亦或者搜狗等等,都能看到各種各樣的信息,文字,圖片,視頻,不知道大家是如何看待這個過程的,以前天真的我以為是各種各樣的人,把自己的內容或者文檔提交給百度的服務器,然后我們從百度的服務器去讀取,搜索,嗯,這個源自於我上高中的時候,搜索各種百度文庫的臆想,這個屬於我認識的搜索 1.0 。
后來我工作了,第一次開始寫 Web ,那個時候經理讓寫 TDK(Title + Description + Key),當時很好奇為什么要這么說,經理說,是為了 SEO ,額好吧,雖然不是很明白,大概懂了——設置好頁面的 TDK 以后,那個搜索引擎就能找到我們的關鍵字,然后我們就可以在搜索引擎中搜到我們的網站了,嗯~聽起來不錯,這個時候,我凌亂了,不是說我們必須存進百度的數據庫,我們才能搜索到么,太神奇了吧,這就是我認識的搜索 2.0 。
轉眼過去一段時間,接觸的也越來越多,這個時候我負責了一個旅游游記的項目,老板說,我們的數據才千級別,比較少,用戶搜索咱們的不是很方便,讓我搞些網上的數據(當然是合法的哈,只是用來展示,划重點),這個時候我才知道了竟然有 爬蟲 這個東西!原來網上的資源可以隨心所欲的獲取(合法的 × 3 !),這個時候回頭看引擎,哦!原來他們的都是爬取的網上的信息,舉個栗子:大家可以在百度上搜索 “老張的哲學 .net core”,會看到咱們的文章,可以看到不僅有題目,還有文章正文,這也就是說,百度爬取的不僅僅是咱們的 TDK ,還有咱們的文章內容,大概這就是我認識的搜索 3.0 了。
所以說,咱們如果需要想讓咱們的文章,網站等被搜索引擎爬取,然后被大家所搜索到,必須要設置 TDK 和有頁面內容,但是這個時候,我們滿心開新的打開我們的 Vue 博客第一版的項目時候,卻發現了這個樣子。。
咱們辛辛苦苦寫的文章並沒有被瀏覽器所生成,自然以后不會被爬取到,如果要是我們的商城也是這樣,那得少了多少流量,都是錢喲,所以說,如果是帶有文章類,內容類,商品類的 Web 項目,一定要解決這個問題,這個就是為什么要 SSR 的第一個原因。
大家可以看看文章最頂部的第二個項目的gif圖,就是通過 Nuxt 實現的服務端渲染,已經在源代碼中有了內容。
2、為 Vue 渲染瓶頸提供幫助
在上面兩個流程中,咱們可以看到,普通的客戶端渲染,和SSR 的服務端渲染的最大區別就在於,Html 片段到底被誰控制,是 Vue 還是服務端,如果是前端,h5請求 API 的時候,因為涉及到跨域,本身除了服務器的限制,還有用戶網絡,寬帶等諸多限制,咱們需要等待入口頁面和 JS 下載成功,才能根據邏輯去獲取數據,中間又會出現很多問題。
並且如果當前頁面邏輯過多,數據過於繁瑣的情況下,我們的 vue 在客戶端渲染也會成為性能瓶頸,最明顯的就是一些電商公司的首頁(比如某寶,那首頁打開真是復雜的要命)首次加載的時候,白屏的出現,loading圖的渲染,着實讓人難堪,雖然現在都采用柵欄預熱,但還是不舒服。這是瓶頸問題,如果單單從 Vue 端來解決這個瓶頸,花費比較大,所以這個時候就用到了 SSR 服務端渲染,而且我們可以使用 Redis 去緩存這些頁面的 Html 片段,我們加載后會更快。
三、如何實現 SSR 服務端渲染 —— 基於 webpack 的簡單程序
注意:今天咱們主要是說明下 SSR 的內容、原理和基本使用,這里簡單說一個小Demo栗子,咱們之后的項目會用到一個框架 Nuxt.js
1、基於 webpack 初始化 npm 項目
新建文件夾 VueSSRDemo3 ,進入之后,執行 npm i ,初始化項目
npm i
會看到一個 package-lock.json 文件,大家還記得這個么,這個簡單來說,是保證項目的一致性,保證團結開發的時候都依賴相同的包,這里再補充下:
1、npm 5.0.x 版本,不管package.json怎么變,npm i 時都會根據lock文件下載
package-lock.json file not updated after package.json file is changed · Issue #16866 · npm/npm
這個 issue 控訴了這個問題,明明手動改了package.json,為啥不給我升級包!然后就導致了5.1.0的問題...
2、npm 5.1.0 版本后 npm install 會無視lock文件 去下載最新的npm
然后有人提了這個issue why is package-lock being ignored? · Issue #17979 · npm/npm
控訴這個問題,最后演變成5.4.2版本后的規則。
3、npm 5.4.2 版本后 why is package-lock being ignored? · Issue #17979 · npm/npm
大致意思是,如果改了package.json,且package.json和lock文件不同,那么執行`npm i`時npm會根據package中的版本號以及語義含義去下載最新的包,並更新至lock。
2、新建 package.json 依賴包文件
{ "name": "ssr", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "server": "webpack --config ./webpack/webpack.server.js", "client": "webpack --config ./webpack/webpack.client.js" }, "author": "laozhang", "license": "ISC", "dependencies": { "axios": "^0.16.0", "babel": "^6.23.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-polyfill": "^6.26.0", "babel-preset-env": "^1.7.0", "body-parser": "^1.18.3", "compression": "^1.7.2", "express": "^4.15.4", "express-http-proxy": "^1.2.0", "gulp": "^3.9.1", "gulp-shell": "^0.6.5", "http-proxy-middleware": "^0.18.0", "less": "^3.0.4", "less-loader": "^4.1.0", "shell": "^0.5.0", "superagent": "^3.8.3", "vue": "^2.2.2", "vue-meta": "^1.5.0", "vue-router": "^2.2.0", "vue-server-renderer": "^2.2.2", "vue-ssr-webpack-plugin": "^3.0.0", "vuex": "^2.2.1", "vuex-router-sync": "^4.2.0" }, "devDependencies": { "babel-core": "^6.26.3", "babel-loader": "^6.4.1", "babel-preset-es2015": "^6.24.1", "css-loader": "^0.28.4", "style-loader": "^0.18.2", "vue-loader": "^11.1.4", "vue-template-compiler": "^2.2.4", "webpack": "^2.7.0" } }
3、執行 npm install 安裝依賴包
npm install
然后就會增加 node_modules 文件夾,這個大家就很熟悉了。
4、新增部分文件,並依次填寫內容
結構如下:
├── dist // 保存我們的打包后的文件 ├── node_modules // 依賴包文件夾 ├── entry // │ └── entry-server.js // 服務端文件 ├── src // 我們的項目的源碼編寫文件 │ ├── views // view存放目錄 │ │ ├── about.vue //about 頁面 │ │ ├── like.vue //like 頁面 │ │ └── Home.vue //Home 頁面 │ └── App.vue // App入口文件 │ └── main.js // 主配置文件 │ └── router.js // 路由配置文件 └── .babelrc // babel 配置文件 └── package.json // 項目依賴包配置文件 └── package-lock.json // npm5 新增文件,優化性能 └── server.js // server 文件 └── README.md // 說明文檔
整體結構就是這樣,然后就是開始寫入代碼了,一共七個文件,大家可以自己試一試,或者直接下載 git 代碼
更新:
下邊沒有具體的更新,只有代碼和部分注釋,大家可以看看我的下一篇文章,更好懂些:
《從壹開始前后端分離 [ Vue2.0+.NetCore2.1] 二十六║Client渲染、Server渲染知多少{補充}》
//1、/* entry-server.js */ import { createApp } from '../src/main' export default context => { return new Promise((resolve, reject) => { const app = createApp() // 更改路由 app.$router.push(context.url) // 獲取相應路由下的組件 const matchedComponents = app.$router.getMatchedComponents() // 如果沒有組件,說明該路由不存在,報錯404 if (!matchedComponents.length) { return reject({ code: 404 }) } resolve(app) }) } //2、三個 vue 頁面,和app vue頁面,就是簡單的寫法,大家可以直接隨意 //3、//main.js import Vue from 'vue' import createRouter from './router' import App from './App.vue' // 導出一個工廠函數,用於創建新的vue實例 export function createApp() { const router = createRouter() const app = new Vue({ router, render: h => h(App) }) return app } /*4、 route.js */ import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) export default function createRouter() { let router = new VueRouter({ // 要記得增加mode屬性,因為#后面的內容不會發送至服務器,服務器不知道請求的是哪一個路由 mode: 'history', routes: [ { alias: '/', path: '/home', component: require('./views/home.vue') }, { path: '/like', component: require('./views/like.vue') }, { path: '/about', component: require('./views/about.vue') } ] }) return router } /* 5、webpack.server.js */ const path = require('path'); const projectRoot = path.resolve(__dirname, '..'); module.exports = { // 此處告知 server bundle 使用 Node 風格導出模塊(Node-style exports) target: 'node', entry: ['babel-polyfill', path.join(projectRoot, 'entry/entry-server.js')], output: { libraryTarget: 'commonjs2', path: path.join(projectRoot, 'dist'), filename: 'bundle.server.js', }, module: { rules: [{ test: /\.vue$/, loader: 'vue-loader', }, { test: /\.js$/, loader: 'babel-loader', include: projectRoot, exclude: /node_modules/, options: { presets: ['es2015'] } }, { test: /\.less$/, loader: "style-loader!css-loader!less-loader" } ] }, plugins: [], resolve: { alias: { 'vue$': 'vue/dist/vue.runtime.esm.js' } } } //6、babelre文件 { "presets": [ "babel-preset-env" ], "plugins": [ "transform-runtime" ] } /*7、 server.js */ const express = require('express')() const renderer = require('vue-server-renderer').createRenderer() const createApp = require('./dist/bundle.server.js')['default'] // 響應路由請求 express.get('*', (req, res) => { const context = { url: req.url } // 創建vue實例,傳入請求路由信息 createApp(context).then(app => { renderer.renderToString(app, (err, html) => { if (err) { return res.state(500).end('運行時錯誤') } res.send(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue2.0 SSR渲染頁面</title> </head> <body> ${html} </body> </html> `) }) }, err => { if(err.code === 404) { res.status(404).end('所請求的頁面不存在') } }) }) // 服務器監聽地址 express.listen(8089, () => { console.log('服務器已啟動!') })
5、打包文件
npm run server
這個時候,你會發現,我們的dist 文件夾內,多了一個 bundle.server.js 文件
6、啟動服務
node server
這個時候我們就可以看到效果了
7、查看源代碼,發現我們的頁面已經渲染成功了!
這樣我們已經達到了 SSR 的作用
四、結語
因為今天時間的問題,咱們我先把核心文件搭建好了,大家知道了什么是 SSR,以及存在的問題和解決辦法,但是具體的文件 entry-server.js 、 webpack-server.js、和server.js 都是什么意思,明天咱們再統一說明,並且順帶給大家引入新的框架 Nuxg.js,一個成熟的 SSR 框架。
五、Github
https://github.com/anjoy8/Blog.Vue/tree/master/Demo/Vue_SSR
下載后,先 npm install 安裝依賴
然后執行打包和啟動服務命令
npm run server
node server
最后訪問 localhost:8089