从壹开始先后端分离 [ Vue2.0+.NET Core2.1] 二十五║初探服务端渲染(我的博客二)

 缘起

时间真快,如今已是这个系列教程的下半部 Vue 第 12 篇了,昨天我也简单思考了下,可能明天再来一篇,Vue 就基本告一段落了,由于什么呢,这里给你们说个题外话,当时写博文的时候,只是想给你们增长点儿学习的动力,天天提醒下,彻底没有提纲或者安排说明什么的,就是按照我本身学的方向走,正好发现了一个规律就是:每个系列正好是 1 个引子 + 12 篇正文,不知道你们对这个有没有感受,你们可能看到个人头像就知道了,哈哈,其实我是一个红迷,正好这里机缘巧合,两个系列都造成了这样的,我自私的给本身画了一个规划,正好是一组判词——十二钗正册,副册,又副册等等(说明:这是我本身的一厢情愿哈,你们若是有红迷爱好者,请不要喷我 [苦笑] ),按照计划,应该会写 9 部,除了引子,正好是 108 篇,哈哈之后的再说吧。这里本身昨天瞎想了一通,若是有红迷爱好者也能够找我哟,加群(867095512)或者我的QQ(3143422472)都行。css

由于 Vue 这个系列仍是不少的要说的,不过基本的我们都说了,你们能够再经过传送门看看《Vue 学习12篇》,今天呢,我们就说说一个老生常谈的问题,就是如何实现 Vue 的 SSR 服务端渲染,你们若是是第一次接触到,可能还比较陌生,不要慌,本文就给你们经过 Nuxt.js 框架,来说解这个问题,而后为了之后实现我们的第二个项目,大体会是这个样子(注意页面的源代码已经有内容了):html

 

 

注意:今天仅仅给你们说明 SSR 服务端渲染,会简单说一个小 DEMO ,上图中的框架具体的代码,在我们的第二个博客项目中会说到,由于这个框架一两篇是说不完的。前端

 

零、今天要完成蓝色区块部分

 

 

1、Vue 的 SSR 究竟是个什么?

 

来自官方的解释:vue

Vue.js 是构建客户端应用程序的框架。默认状况下,能够在浏览器中输出 Vue 组件,进行生成 DOM 和操做 DOM。然而,也能够将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上彻底可交互的应用程序。node

服务端渲染的 Vue.js 应用程序也能够被认为是"同构"或"通用",由于应用程序的大部分代码均可以在服务器和客户端上运行。webpack

羞涩难懂ios

个人我的理解就是:git

一、目前Vue的模式是:github

在生成页面的工做中,咱们如今是把组件放在浏览器里,而后把 Data 填充到组件中生成 DOM,这也是通常的异步操做的动做,我们平时必定是这么操做的,先在页面写上 DIV,而后用 Jquery 获取数据,把数据填充到 DIV 里web

 

二、SSR的模式呢,转变成了先在服务器中 Data 先把组件先渲染成 Html 字符串,当成静态资源,就像 css 字符串那样,再抛到前台页面。

这第二种就是 SSR 服务端渲染,你们应该发现了这个和普通的区别——就是渲染html片断的控制权转向了服务端,那为何要这么作呢?请往下看。

 注意:这里的服务端,并非在咱们的.net core api 中生成的,而是在vue中,咱们经过webpack 打包后 node server来处理的。

2、为何要 SSR 服务端渲染?

一、首先我们须要说说搜索引擎 —— SEO

我们打开任何搜索引擎,不管是谷歌,仍是百度,亦或者搜狗等等,都能看到各类各样的信息,文字,图片,视频,不知道你们是如何看待这个过程的,之前天真的我觉得是各类各样的人,把本身的内容或者文档提交给百度的服务器,而后咱们从百度的服务器去读取,搜索,嗯,这个源自于我上高中的时候,搜索各类百度文库的臆想,这个属于我认识的搜索 1.0 。

后来我工做了,第一次开始写 Web ,那个时候经理让写 TDK(Title + Description + Key),当时很好奇为何要这么说,经理说,是为了 SEO ,额好吧,虽然不是很明白,大概懂了——设置好页面的 TDK 之后,那个搜索引擎就能找到咱们的关键字,而后咱们就能够在搜索引擎中搜到咱们的网站了,嗯~听起来不错,这个时候,我凌乱了,不是说咱们必须存进百度的数据库,咱们才能搜索到么,太神奇了吧,这就是我认识的搜索 2.0 。

转眼过去一段时间,接触的也愈来愈多,这个时候我负责了一个旅游游记的项目,老板说,咱们的数据才千级别,比较少,用户搜索我们的不是很方便,让我搞些网上的数据(固然是合法的哈,只是用来展现,划重点),这个时候我才知道了居然有 爬虫 这个东西!原来网上的资源能够为所欲为的获取(合法的 × 3 !),这个时候回头看引擎,哦!原来他们的都是爬取的网上的信息,举个栗子:你们能够在百度上搜索  “老张的哲学 .net core”,会看到我们的文章,能够看到不只有题目,还有文章正文,这也就是说,百度爬取的不只仅是我们的 TDK ,还有我们的文章内容,大概这就是我认识的搜索 3.0 了。

 

因此说,我们若是须要想让我们的文章,网站等被搜索引擎爬取,而后被你们所搜索到,必需要设置 TDK 和有页面内容,可是这个时候,咱们满心开新的打开咱们的 Vue 博客初版的项目时候,却发现了这个样子。。

 

我们辛辛苦苦写的文章并无被浏览器所生成,天然之后不会被爬取到,若是要是咱们的商城也是这样,那得少了多少流量,都是钱哟,因此说,若是是带有文章类,内容类,商品类的 Web 项目,必定要解决这个问题,这个就是为何要 SSR 的第一个缘由。

你们能够看看文章最顶部的第二个项目的gif图,就是经过 Nuxt 实现的服务端渲染,已经在源代码中有了内容。 

 

二、为 Vue 渲染瓶颈提供帮助

在上面两个流程中,我们能够看到,普通的客户端渲染,和SSR 的服务端渲染的最大区别就在于,Html 片断到底被谁控制,是 Vue 仍是服务端,若是是前端,h5请求 API 的时候,由于涉及到跨域,自己除了服务器的限制,还有用户网络,宽带等诸多限制,我们须要等待入口页面和 JS 下载成功,才能根据逻辑去获取数据,中间又会出现不少问题。

而且若是当前页面逻辑过多,数据过于繁琐的状况下,咱们的 vue 在客户端渲染也会成为性能瓶颈,最明显的就是一些电商公司的首页(好比某宝,那首页打开真是复杂的要命)首次加载的时候,白屏的出现,loading图的渲染,着实让人难堪,虽然如今都采用栅栏预热,但仍是不舒服。这是瓶颈问题,若是单单从 Vue 端来解决这个瓶颈,花费比较大,因此这个时候就用到了 SSR 服务端渲染,并且咱们可使用 Redis 去缓存这些页面的 Html 片断,咱们加载后会更快。

 

 3、如何实现 SSR 服务端渲染 —— 基于 webpack 的简单程序

注意:今天我们主要是说明下 SSR 的内容、原理和基本使用,这里简单说一个小Demo栗子,我们以后的项目会用到一个框架 Nuxt.js

一、基于 webpack 初始化 npm 项目

新建文件夹 VueSSRDemo3 ,进入以后,执行  npm i ,初始化项目

 npm i

会看到一个 package-lock.json 文件,你们还记得这个么,这个简单来讲,是保证项目的一致性,保证团结开发的时候都依赖相同的包,这里再补充下:

一、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的问题...

二、npm 5.1.0 版本后 npm install 会无视lock文件 去下载最新的npm

而后有人提了这个issue why is package-lock being ignored? · Issue #17979 · npm/npm

控诉这个问题,最后演变成5.4.2版本后的规则。

三、npm 5.4.2 版本后 why is package-lock being ignored? · Issue #17979 · npm/npm

大体意思是,若是改了package.json,且package.json和lock文件不一样,那么执行`npm i`时npm会根据package中的版本号以及语义含义去下载最新的包,并更新至lock。

 

二、新建 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"
  }
}

三、执行 npm install 安装依赖包

npm install

而后就会增长 node_modules 文件夹,这个你们就很熟悉了。

 

四、新增部分文件,并依次填写内容

结构以下:

├── 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渲染知多少{补充}

 

//一、/* 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)
    })

}
//二、三个 vue 页面,和app vue页面,就是简单的写法,你们能够直接随意

//三、//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
}


/*四、 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
}

/* 五、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'
        }
    }
}

//六、babelre文件
{
  "presets": [
    "babel-preset-env"
  ],
  "plugins": [
    "transform-runtime"
  ]
}


/*七、 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('服务器已启动!')
})

 

五、打包文件

npm run server

这个时候,你会发现,咱们的dist 文件夹内,多了一个 bundle.server.js 文件

 

六、启动服务

 node server

这个时候咱们就能够看到效果了

七、查看源代码,发现咱们的页面已经渲染成功了!

这样咱们已经达到了 SSR 的做用

 

 

4、结语

由于今天时间的问题,我们我先把核心文件搭建好了,你们知道了什么是 SSR,以及存在的问题和解决办法,可是具体的文件 entry-server.js 、 webpack-server.js、和server.js 都是什么意思,明天我们再统一说明,而且顺带给你们引入新的框架 Nuxg.js,一个成熟的 SSR 框架。

 

5、Github

https://github.com/anjoy8/Blog.Vue/tree/master/Demo/Vue_SSR

下载后,先 npm install 安装依赖

而后执行打包和启动服务命令

npm run server
node server

 最后访问 localhost:8089