资讯专栏INFORMATION COLUMN

从零开始搭建一个vue-ssr(上)

Winer / 2200人阅读

摘要:从零开始搭建一个背景是什么全拼是,服务端渲染。大家不妨可以打开一些页面或者一些公司的网站,查看源代码,你会发现,也是有这个标记。这时候,我们发现页面的路由切换生效了,并且不同页面的源代码也不一样了。从零开始搭建一个下项目源码

从零开始搭建一个vue-ssr 背景 What?SSR是什么?

SSR全拼是Server-Side Rendering,服务端渲染。
所谓服务端渲染,指的是把vue组件在服务器端渲染为组装好的HTML字符串,然后将它们直接发送到浏览器,最后需要将这些静态标记混合在客户端上完全可交互的应用程序。

Why?为什么选择SSR?

①满足seo需求,传统的spa数据都是异步加载的,爬虫引擎无法加载,需要利用ssr将数据直出渲染在页面源代码中。
②更宽的内容达到时间(首屏加载更快),当请求页面的时候,服务端渲染完数据之后,把渲染好的页面直接发送给浏览器,并进行渲染。浏览器只需要解析html不需要去解析js。

How?SSR的原理

借用下面的一张图,我们来简单阐述一下vue-ssr的原理。

我们可以看到,左侧Source部分就是我们所编写的源代码,所有代码有一个公共入口,就是app.js,紧接着就是服务端的入口
(entry-server.js)和客户端的入口(entry-client.js)。当完成所有源代码的编写之后,我们通过webpack的构建,打包出两个bundle,分别是server bundle和client bundle;当用户进行页面访问的时候,先是经过服务端的入口,将vue组建组装为html字符串,并混入客户端所访问的html模板中,最终就完成了整个ssr渲染的过程。

开始搭建 创建一个空白目录并初始化

在终端输入以下命令

mkdir ssr-demo
cd ssr-demo
npm init

由于我们这个只是一个demo项目,可以直接一路按回车键,直接忽略配置。
完成之后我们可以看到文件夹里面有一个package.json的文件,这就是配置表。

安装依赖

该项目需要四个依赖,依次安装

npm install express
npm install vue
npm install vue-router
npm install vue-server-renderer

其中express使我们node端的框架,vue用于创建vue实例,vue-router则用于实现路由控制,最后vue-server-renderer尤为关键,我们实现的vue-ssr依靠于这个库提供的API。
在安装依赖完毕之后,我们看到package.json中已经把四个依赖都写上了。

"express": "^4.17.1",
"vue": "^2.6.10",
"vue-router": "^3.0.6",
"vue-server-renderer": "^2.6.10"
创建一个node服务

在根目录下我们新建一个server.js,用户搭建node服务

const express = require("express");
const app = express();

app.get("*", (request, response) => {
    response.end("hello, ssr");
})

app.listen(3001, () => {
    console.log("服务已开启")
})

接着为了后续开发的便利,我们在package.json中添加一个启动命令:

"scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "server": "node index.js"
 },

接着我们在终端输入 npm run server,然后再浏览器输入localhost:3001,便可以看到页面中的文字被成功渲染。

渲染html页面

在上一步我们已经能成功渲染出一个文字,但是ssr并不是主要为了渲染文字,而是渲染一个html模板。
那么,接下来,我们得告知浏览器,我们需要渲染的是html,而不只是text,因此我们需要修改响应头。
同时,引入vue-server-renderer中的createRenderer对象,有一个renderToString的方法,可以将vue实例转成html的形式。(renderToString这个方法接受的第一个参数是vue的实例,第二个参数是一个回调函数,如果不想使用回调函数的话,这个方法也返回了一个Promise对象,当方法执行成功之后,会在then函数里面返回html结构。)
修改server.js如下:

const express = require("express");
const app = express();
const Vue = require("vue");
const vueServerRender = require("vue-server-renderer").createRenderer();

app.get("*", (request, response) => {
    const vueApp = new Vue({
        data:{
           message: "hello, ssr"
        },
        template: `

{{message}}

` }); response.status(200); response.setHeader("Content-type", "text/html;charset-utf-8"); vueServerRender.renderToString(vueApp).then((html) => { response.end(html); }).catch(err => console.log(err)) }) app.listen(3001, () => { console.log("服务已开启") })

保存代码,重启服务,然后重新刷新页面。我们发现,页面好像没什么不同,就是字体变粗了而已。其实并不是,你可以尝试查看页面源代码,我们发现在源代码中,已经存在一个标签对h1,这就是html模板的雏形。同时,细心的同学还会发现,h1上面有一个属性:
data-server-rendered="true",那这个属性是干什么的呢?这个是一个标记,表明这个页面是由vue-ssr渲染而来的。大家不妨可以打开一些seo页面或者一些公司的网站,查看源代码,你会发现,也是有这个标记。
虽然h1标签对被成功渲染,但是我们发现这个html页面并不完整, 他缺少了文档声明,html标签,body标签,title标签等。

将Vue实例挂载进html模板中

创建一个index.html,用于挂载Vue实例。







Hello, SSR


    

注意,body中的注释不能去掉,这是Vue挂载的占位符。
然后修改server.js,将html模板引进去。这里我们在createRenderer函数可以接收一个对象作为配置参数。配置参数中有一项为template,这项配置的就是我们即将使用的Html模板。这个接收的不是一个单纯的路径,我们需要使用fs模块将html模板读取出来。

let path = require("path");
const vueServerRender = require("vue-server-renderer").createRenderer({
    template:require("fs").readFileSync(path.join(__dirname,"./index.html"),"utf-8")
});

保存代码,重启服务,然后重新刷新页面。我们查看源代码,发现,已经能成功渲染出一个完整的页面了。

创建一个Vue项目的开发目录

上面的开发模式,很显然只是一个demo而已,接下来我们模拟一下正常的vue开发的目录结构。
创建一个src文件夹,里面有一个router文件夹,再有一个index,js用作路由,并创建一个app.js,用作vue的入口,如下图:

修改router/index.js

const vueRouter = require("vue-router");
const Vue = require("vue");

Vue.use(vueRouter);

module.exports = () => {
    return new vueRouter({
        mode:"history",
        routes:[
            {
                path:"/",
                component:{
                    template:`

this is home page

` }, name:"home" }, { path:"/about", component:{ template:`

this is about page

` }, name:"about" } ] }) }

修改app.js

const Vue = require("vue");
const createRouter = require("./router")

module.exports = (context) => {
    const router = createRouter();
    return new Vue({
        router,
        data:{
            message:"Hello,Vue SSR!",
        },
        template:`
            

{{message}}

  • home
  • about
` }); }

然后在server.js中,将app.js引入

const express = require("express");
const app = express();
const vueApp = require("./src/app.js");

let path = require("path");
const vueServerRender = require("vue-server-renderer").createRenderer({
    template:require("fs").readFileSync(path.join(__dirname,"./index.html"),"utf-8")
});

app.get("*", (request, response) => {
    let vm = vueApp({});

    response.status(200);
    response.setHeader("Content-type", "text/html;charset-utf-8");

    vueServerRender.renderToString(vm).then((html) => {
        response.end(html);
    }).catch(err => console.log(err))
})

app.listen(3001, () => {
    console.log("服务已开启")
})

保存代码,重启服务,然后重新刷新页面。然后我们可以看到浏览器的路由已经被成功渲染了,但是无论怎么点击都没反应,浏览器的url有更改,但是页面内容不变。
这是因为我们只是将页面渲染的工作交给服务端,而页面路由切换,还是在前端执行,服务端并未能接收到该指令,因此无论怎么切换路由,服务端渲染出来的页面根本没变化。

实现服务端控制页面路由

在src中创建一个entry-server.js文件,该文件为服务端入口文件,接收app和router实例:

const createApp = require("./app.js");

module.exports = (context) => {
    return new Promise(async (reslove,reject) => {
        let {url} = context;

        let {app,router} = createApp(context);
        router.push(url);
        //  router回调函数
        //  当所有异步请求完成之后就会触发
        router.onReady(() => {
            let matchedComponents = router.getMatchedComponents();
            if(!matchedComponents.length){
                return reject();
            }
            reslove(app);
        },reject)
    })
}

在src中创建一个entry-client.js文件,该文件为客户端入口,负责将路由挂载到app里面。

const createApp = require("./app.js");
let {app,router} = createApp({});

router.onReady(() => {
    app.$mount("#app")
});

修改app.js,将router和vue实例暴露出去

const Vue = require("vue");
const createRouter = require("./router")

module.exports = (context) => {
    const router = createRouter();
    const app =  new Vue({
        router,
        data:{
            message:"Hello,Vue SSR!",
        },
        template:`
            

{{message}}

  • home
  • about
` }); return { app, router } }

最终修改server.js

const express = require("express");
const app = express();

const App = require("./src/entry-server.js");

let path = require("path");
const vueServerRender = require("vue-server-renderer").createRenderer({
    template:require("fs").readFileSync(path.join(__dirname,"./index.html"),"utf-8")
});

app.get("*", async(request, response) => {

    response.status(200);
    response.setHeader("Content-type", "text/html;charset-utf-8");

    let {url} = request;
    let vm;
    vm = await App({url})
    vueServerRender.renderToString(vm).then((html) => {
        response.end(html);
    }).catch(err => console.log(err))
})

app.listen(3001, () => {
    console.log("服务已开启")
})

保存代码,重启服务,然后重新刷新页面。这时候,我们发现页面的路由切换生效了,并且不同页面的源代码也不一样了。

数据传递

既然是服务端渲染,数据的接收也是来源于服务端,那怎样才能把服务端接收到的数据传输给前端,然后进行渲染呢?
修改entry-server.js,进行同步或者异步获取数据

const createApp = require("./app.js");

const getData = function(){
    return new Promise((reslove, reject) => {
        let str = "this is a async data!";
        reslove(str);
    })
}

module.exports = (context) => {
    return new Promise(async (reslove,reject) => {
        let {url} = context;

        // 数据传递
        context.propsData = "this is a data from props!"

        context.asyncData = await getData();

        let {app,router} = createApp(context);
        router.push(url);
        //  router回调函数
        //  当所有异步请求完成之后就会触发
        router.onReady(() => {
            let matchedComponents = router.getMatchedComponents();
            if(!matchedComponents.length){
                return reject();
            }
            reslove(app);
        },reject)
    })
}

修改app.js,接收数据并渲染

const Vue = require("vue");
const createRouter = require("./router")

module.exports = (context) => {
    const router = createRouter();
    const app =  new Vue({
        router,
        data:{
            message:"Hello,Vue SSR!",
            propsData: context.propsData,
            asyncData: context.asyncData
        },
        template:`
            

{{message}}

{{asyncData}}

{{propsData}}

  • home
  • about
` }); return { app, router } }

最后我们可以看到无论是同步还是异步获取的数据,都能成功地通过服务端渲染,展示在页面源代码中。
另外,你也可以在server.js中的request中,将数据传递下去。

总结

实现了一个简易版本的vue-ssr,下期我们会依赖于vue-cli,进行webpack改造,实现一个通用且更实用的vue-ssr框架。从零开始搭建一个vue-ssr(下)

项目源码

https://github.com/TheWalking...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105156.html

相关文章

  • 从零开始搭建一个vue-ssr(下)

    摘要:开始改建补充安装依赖与上一次不同,这次我们基于进行改建,已经有了很多依赖库了,但我们任需要补充一个核心修改客户端的配置修改文件,添加插件添加了这个配置以后,重新启动项目通过地址就可以访问到,页面中出现的内容就是所需要的。 从零开始搭建一个vue-ssr 前言 上次我们已经实现了从零开始,搭建一个简单的vue-ssr的demo:从零开始搭建一个vue-ssr(上)。那么这次呢,我们基于v...

    Jochen 评论0 收藏0
  • vue服务端渲染demo将vue-cli生成的项目转为ssr

    摘要:无需使用服务器实时动态编译,而是使用预渲染方式,在构建时简单地生成针对特定路由的静态文件。与可以部署在任何静态文件服务器上的完全静态单页面应用程序不同,服务器渲染应用程序,需要处于运行环境。更多的服务器端负载。 目录结构 -no-ssr-demo 未做ssr之前的项目代码用于对比 -vuecli2ssr 将vuecli生成的项目转为ssr -prerender-demo 使用prer...

    whinc 评论0 收藏0
  • 用vue搭建的个人博客介绍----mapblog小站

    摘要:后端主要使用的框架,数据库采用。后台管理登录采用与后端进行登陆状态的确认。本文首发于小站,这是一个积累和分享知识的个人博客 这篇文章搁置了很长时间,最终决定还是把它写出来,给刚开始学习vue并且想用vue写个人博客的同学一个参考。因为当初我也是参考了其他人分享的知识,从一个vue小白变成了一个入门级选手,并最终完成了这个个人博客的搭建工作,代码已托管在Github-justJokee。...

    Ashin 评论0 收藏0
  • 每个前端猿都有一个开发属于自己技术博客的心

    摘要:总算是今天成功把自己的孩子托付到阿里云的服务器上面了。中间还遇到很多很多坑最后看这自己所部署的三个网站安静的躺在自己租的阿里云上。 一把桌子,一台电脑,一瓶红牛,一包纸巾,从白天到黑夜。历经一个多月的时间,从零到构思到设计,从设计到vue-ssr 的框架设计,然后再从前端的业务逻辑代码的实现,从 后台nodejs 的 koa2框架到数据库的设计到后端的业务逻辑的代码实现,从购买阿里云服...

    wslongchen 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<