從壹開始前后端分離 [ Vue2.0+.NET Core2.1] 二十五║初探服務端渲染(個人博客二)


 緣起

時間真快,現在已經是這個系列教程的下半部 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 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM