koa框架23 服务端渲染、jade、ejs、koa服务端渲染、内容静态化
服务端渲染
全称的 server side rendering 也就是所谓的ssr了,经常看到vue的一个关于seo优化的一个方案或者是防止首页白屏,一般选用ssr方案。
优点:
- seo友好
- 无兼容问题
- 安全性高
- 代码精简
相对客户端渲染 client side rendering 客户端有两个优点:
- 降低服务端,带宽压力
- 用户体验好
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会自动在到期后删除内容。
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据