服务端渲染

全称的 server side rendering 也就是所谓的ssr了,经常看到vue的一个关于seo优化的一个方案或者是防止首页白屏,一般选用ssr方案。

优点:

  1. seo友好
  2. 无兼容问题
  3. 安全性高
  4. 代码精简

相对客户端渲染 client side rendering 客户端有两个优点:

  1. 降低服务端,带宽压力
  2. 用户体验好

js运行速度快,生成html比服务端一个来回快很多。

服务端渲染框架

pug(jade)

以前叫jade,现在叫pug,改名了。

使用

npm i pug
const pug=require('pug');

pug.renderFile(
  //模板
  './template/1.pug',
  //数据
  {
    pretty: true,    //用来测试
    title: '试试pug这个东西',
    users: [
      {name: 'blue', age: 18},
      {name: 'zhangsan', age: 25, vip: true},
      {name: 'lisi', age: 26},
    ]
  },



  (err, html)=>{
  if(err){
    console.log('渲染失败');
    console.log(err);
  }else{
    console.log(html);
  }
});

pug.render()这是渲染手打的pug代码,renderFile则是渲染一个模板文件,所以renderFile的第一个参数为模板文件地址。

第二个参数是模板使用的参数+pug自身的设置选项,他合并到一个对象里面了,pretty: true,用于美化输出的,原来是做一行压缩的

第三个则是回调,err为错误,html为解析完毕后渲染好的html文档。

模板

html
  head
    meta(charset="utf-8")
    meta(name="copyright",content="zhinengshe.com")
    link(rel="stylesheet",href="/main.css")
    style.
      #logo {width:200px;height:150px;}
      .box {width:200px;height:200px;}
  body
    h1#logo.box.active= title
    ul.list
      for user in users
        if user.vip
          li.item.vip
            span.name= user.name
            span.age= user.age
        else
          li.item
            span.name= user.name
            span.age= user.age

pug的语法,层级的关系是通过他的缩进来判断的,行内属性写在圆括号后,然后由于id和class比较常用,于是又对应的缩写形式:h1#logo.box.active

如果是元素的内容,在标签书写完毕后空一格接内容:style #logo{xxxx}

如果想要换行写,那么就在标签后面加个点.,然后回车,记得缩进,不然不会识别为内容

如果要使用变量,在前面加个等号=

然后就是一些for循环了。

ejs

ejs相对于pug,他不会破坏html,他类似于php那种渲染方式。

npm i ejs

使用

const ejs=require('ejs');

ejs.renderFile(
  //1.模板
  './template/1.ejs',
  //2.数据
  {
    name: 'blue',
    arr: [12,5,8,34]
  },
  (err, html)=>{
    if(err){
      console.log(err);
    }else{
      console.log(html);
    }
  }
);

ejs也是三个参数,和pug差不多,但是ejs第二个参数没有配置参数

模板

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <%-include('header.inc.ejs')%>

    姓名:<%=name%>
    <ul>
      <%arr.forEach(item=>{%>
        <li><%=item%></li>
      <%})%>
    </ul>
  </body>
</html>

ejs实际上就是一个html+变量语法

<%=name%> name是变量,=符号用于输出,这个输出是转义的,如果变量里面有html的尖括号那些特殊字符,会被转义

<%-name%> 使用减号表示不转义输出

include是ejs的一个引入ejs文件的一个方法,用于多个文件共用一部分相同的内容,比如header,footer这些。

然后include本身只是引用,所以还需要输出,如果使用=等号,引入的ejs文件会被转义,所以要使用减号

js部分,ejs里面可以直接使用原生js,js的使用,如果不需要输出,不要等号和减号就行了,所以<%arr.forEach(item=>{%><%%>直接包裹js语法就行了。

koa渲染

koa对ejs也有也插件

npm i koa-ejs
const Koa = require("koa");
const ejs = require("koa-ejs");
const path = require("path");


let server = new Koa();


ejs(server, {
    root: path.resolve(__dirname, "template"), //模板目录
    layout: false, //布局目录,子目录
    viewExt: "ejs", //模板扩展名
    cache: false, //缓存
    debug: false, //debug模式,一般用不到
});


server.use(async ctx => {
    await ctx.render("index", {
        title: "木灵鱼儿"
    })
});

server.listen(8080);

koa-ejs的使用时将server作为参数传入,然后接一个配置参数,root为模板根目录,layout暂时不管,false就行了,然后就是viewExt,设置了扩展名后,我们在后面调用模板就不用再写后缀了,缓存的话,如果开启了,会缓存渲染后数据,方便下次调用,自己调试的时候就关闭,debug是作者自己用的,我们一般用不着

使用了通过ctx.render方法,可以去渲染指定模板文件,第一个是文件名,第二个是参数。

配合数据库

const Koa = require('koa');
const ejs = require('koa-ejs');
const path = require('path');
const Router = require('koa-router');
const mysql = require('promise-mysql');
const opn = require('opn');
const {
    time2date
} = require('./libs/common');

let server = new Koa();

ejs(server, {
    //模板目录
    root: path.resolve(__dirname, 'template'),
    //布局目录(子目录)
    layout: false,
    //模板扩展名
    viewExt: 'ejs',

    //缓存
    cache: false,
    debug: false
});

//路由
let router = new Router();

//首页
router.get('/', async ctx => {
    ctx.redirect(
        router.url('list', 1)
    );
});

//列表
router.get('list', '/list/:page', async ctx => {
    const {
        page
    } = ctx.params;
    const SIZE = 10;

    //本页数据
    let data = await ctx.db.query("SELECT ID,title,time,channel FROM news_table LIMIT ?,?", [(page - 1) * SIZE, SIZE]);

    data.forEach(news => {
        news.time = time2date(news.time * 1000);
    });

    //总数据条数
    let rows = await ctx.db.query("SELECT count(*) AS total FROM news_table");

    await ctx.render('list', {
        list: data,
        page: parseInt(page),
        page_count: Math.ceil(rows[0].total / SIZE),
        channels: {
            'index': '热门',
            'guonei': '国内',
            'guoji': '国际',
            'war': '军事',
            'hangkong': '航空'
        }
    });
});

//详情
router.get('/news/:id', async ctx => {
    const {
        id
    } = ctx.params;

    let rows = await ctx.db.query("SELECT title,content FROM news_table WHERE ID=?", [id]);
    let data = rows[0];

    if (!data) {
        ctx.body = '新闻没有找到,可能已经删除或尚未审核';
    } else {
        await ctx.render('news', data);
    }
});

server.use(router.routes());


(async () => {
    server.context.db = await mysql.createPool({
        host: 'localhost',
        user: 'root',
        password: '123456',
        database: 'news163'
    });

    server.listen(8080);
    opn('http://localhost:8080/');
})();

内容静态化

所谓额内容静态化就是将已渲染的东西缓存起来,下次使用相同的东西就使用缓存,已加快速度。

原理

有缓存 ---- 读取缓存 --- 返回

无缓存 ---- 渲染----服务器返回个浏览器渲染内容 --- 将渲染缓存

无非就这个逻辑,于是几种方式

内存缓存

//缓存对象
let cache = {};

//静态化
router.use(async (ctx, next) => {
    if (cache[ctx.url]) {
        console.log(`from cache: ${ctx.url}`);
        ctx.body = cache[ctx.url];
    } else {
        console.log(`from render: ${ctx.url}`);
        await next();

        cache[ctx.url] = ctx.body;
    }
});

通过use,每个路由都需要经过他,我们就就判断嘛,有缓存用缓存,没缓存先渲染丢给服务器后再保存到缓存对象。

文件缓存

将缓存保存为本地文件: 创建一个cache文件夹

const fs = require('promise-fs');

//静态化
router.use(async (ctx, next) => {
    let name = ctx.url.replace(/\//g, '_');
    let cachepath = path.resolve(__dirname, 'cache', name);

    try {
        await fs.stat(cachepath);
        ctx.set('content-type', 'text/html; utf-8');
        ctx.body = fs.createReadStream(cachepath);

        console.log(`from cache: ${ctx.url}`);
    } catch (e) {
        await next();
        await fs.writeFile(cachepath, ctx.body);

        console.log(`from render: ${ctx.url}`);
    }

});

由于我们使用路径作为文件名,但是路径是带下划线的,我们需要进行修改,改为下划线。

然后我们先生成缓存的文件路径,因为这时我们已经有访问路径了嘛,自然知道缓存的文件名了。

由于需要判断文件是否存在,通过stat方法查找是否有这个文件,有就继续运行代码,没有就会报错,所以我们用try-catch包裹

有这个文件,我们通过createReadStream方法,读取一段返回一段,但是这是文件流的形式,也就是文件下载的形式,这就导致文件变成了下载而不是网页,但是我们可以通过修改ctx的头信息将这个流额形式改为html读取。

如果没有这个文件,我们就先渲染,也就是next下一步,渲染自然是通过下一个对应路由去做了,渲染完毕后再保存本地。

radis缓存

使用文件的话,io的消耗很大,我们又可以使用radis

const redis = require('redis');
const bluebird = require('bluebird');


let client = redis.createClient({
    host: 'localhost'
});
bluebird.promisifyAll(redis.RedisClient.prototype);

//静态化
router.use(async (ctx, next) => {
    let name = ctx.url.replace(/\//g, '_');

    let cache = await client.getAsync(name);

    if (cache) {
        ctx.body = cache;

        console.log(`from cache: ${ctx.url}`);
    } else {
        await next();
        await client.psetexAsync(name, 30 * 86400 * 1000, ctx.body);

        console.log(`from render: ${ctx.url}`);
    }
});

连接好radis,并且通过bluebird将方法转为async后

一样先改下路径名字作为缓存对象的key,然后通过getAsync直接去获取这个key,如果这个key存在的话,radis是返回字符的,不像mysql返回数组对象额。

我们直接if判断这个对象存不存在就行了,存在就用,不存在就先next,再存储,这里我们也使用了过期时间,这样radis会自动在到期后删除内容。

分类: Node 标签: jadeejskoanoepug内容静态化

评论

暂无评论数据

暂无评论数据

目录