(Server Side Rendering) :传统的渲染方式,由服务端把渲染的完整的页面吐给客户端。这样减少了一次客户端到服务端的一次http请求,加快相应速度,一般用于首屏的性能优化。
SSR优点:
例如SEO–因为访问一个请求,返回的就是页面全部的HTML结构,包含所需要呈现的所有数据,于是例如搜索引擎或者爬虫的数据抓取;
目前使用MV*架构的项目,大都是前后端分离,数据都是动态生成,不利于SEO优化 利于首屏渲染性能高–首屏的页面加载来自于服务器,不依赖与服务端的接口请求再数据处理;
SSR缺点:
性能全都依赖于服务器 前端界面开发可操作性不高
CSR(Client Side Rendering):是一种目前流行的渲染方式,它依赖的是运行在客户端的JS,用户首次发送请求只能得到小部分的指引性HTML代码。第二次请求将会请求更多包含HTML字符串的JS文件。
FP最快 客户端体验较好,因为在数据没更新之前,页面框架和元素是可以在dom生成的
FP:仅有一个 div 根节点。以VUE为例,div#app 注册一个空的div FCP:包含页面的基本框架,但没有数据内容。以VUE为例,每个template中的div框架,对应VUE生命周期的mounted FMP:包含页面所有元素及数据。以VUE为例,通过接口更新到页面的数据后完整的页面展示;对应VUE的生命周期中的updated
简而言之,就是数据拼接HTML字符串这件事放在服务端还是客户端造成了两者区别。
不利于SEO–爬虫数据不好爬呀~~ 整体加载完速度慢
服务器端渲染的优势在于首屏渲染速度块,简单来讲它不需要来回多次往返于客户端和服务端。但是其性能等众多因素会影响用户体验,比如说:网速,在线活跃人数,服务器的物理位置等等。而客户端渲染则和服务端渲染相反,因为多次和服务器的交互导致首屏加载速度慢。但一旦这些请求完成之后,用户和页面之间的交互时用户体验就会好很多。
用一个现实生活的例子来看:假如从超市买东西吃,以SSR的角度来看,你每次在超市买完随即吃完再走,每次饿了都需要出发去超市。而从CSR的角度来看,就是你从超市购买许多原材料再拿回家去自己煮,多了能放冰箱,这样每次肚子饿了就不需要每次都往超市跑,唯一麻烦一点在于你得花时间挑选食材。
1、优化首屏加载,减少白屏时间,提升加载性能:
2、加速或减少HTTP请求损耗:使用CDN加载公用库,使用强缓存和协商缓存,使用域名收敛,小图片使用Base64代替,使用Get请求代替Post请求,设置 Access-Control-Max-Age 减少预检请求,页面内跳转其他域名或请求其他域名的资源时使用浏览器prefetch预解析等;
3、延迟加载:非重要的库、非首屏图片延迟加载,SPA的组件懒加载等;
4、减少请求内容的体积:开启服务器Gzip压缩,JS、CSS文件压缩合并,减少cookies大小,SSR直接输出渲染后的HTML等;
5、浏览器渲染原理:优化关键渲染路径,尽可能减少阻塞渲染的JS、CSS;
6、优化用户等待体验:白屏使用加载进度条、loading图、骨架屏代替等;
强缓存是利用http头中的Expires和Cache-Control两个字段来控制的,用来表示资源的缓存时间 协商缓存:浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-modify是一个时间标识该资源的最后修改时间;当浏览器再次请求该资源时,request的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存;其中Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识
Access-Control-Max-Age:缓存可以被缓存的时间
SPA 只有一张Web页面的应用,是加载单个HTML页面并在用户与应用程序交互时动态更新该页面的Web应用程序,是指在浏览器中运行的应用,在使用期间不会重新加载页面。像所有的应用一样,它旨在帮助用户完成任务,比如“编写文档”或者“管理Web服务器”
域名发散就是为了突破浏览器对于同一域名并发请求数的限制,使用域名发散为同一个服务申请多个域名,从而可以一定程度上提高并发量;当然,由于建立新的请求需要一定的代价,因此需要在域名发散与域名收敛间进行trade off,通常发散的域名个数为2-4个;
域名收敛就是将静态资源放在一个域名下不进行发散,这主要是为了适应移动端的发展需求;通常DNS是一个开销较大的操作,而移动端由于网络带宽和实时性、资源等的限制,这些开销对移动端的用户体验是致命的,因此需要进行域名收敛;
域名发散是pc端为了利用浏览器的多线程并行下载能力。而域名收敛多用与移动端,提高性能,因为dns解析是是从后向前迭代解析,如果域名过多性能会下降,增加DNS的解析开销。
DNS 预解析:浏览器会在加载网页时对网页中的域名进行解析缓存,这样在你单击当前网页中的连接时就无需进行DNS的解析,减少用户等待时间,提高用户体验。
Gzip页面压缩,像服务器发送压缩文件,同时服务器需要设置解析
简而言之,SSR强在首屏渲染。而CSR强在用户和页面多交互的场景
art-template 是一个简约、超快的模板引擎。 它采用作用域预声明的技术来优化模板渲染速度,从而获得接近 JavaScript 极限的运行性能,并且同时支持 NodeJS 和浏览器。
art-template 既可以用在前端,也可以用在后端 当CSR时前端直接使用art-template根据通信的数据渲染页面 当SSR时node也可以渲染生成静态页面或者将静态页面直接发回给前端
npm i art-template -S
Document
art分为两种语法,标准语法与原始语法。标准语法可以让模板易读写,而原始语法拥有强大的逻辑表达能力。
输出的语法:
标准语法:
{{value}}
{{data.key}}
{{data['key']}}
{{a ? b : c}}
{{a || b}}
{{a + b}}
原始语法:
<%= value %>
<%= data.key %>
<%= data['key'] %>
<%= a ? b : c %>
<%= a || b %>
<%= a + b %>
模板一级特殊变量:
<%= $data %>
原文输出的语法:
标准语法:
{{@ value }}
原始语法:
<%- value %>
原文输出语句不会对 HTML 内容进行转义处理,可能存在安全风险,请谨慎使用。
条件
标准语法:
{{if value}} ... {{/if}}
{{if v1}} ... {{else if v2}} ... {{/if}}
原始语法:
<% if (value) { %> ... <% } %>
<% if (v1) { %> ... <% } else if (v2) { %> ... <% } %>
循环
标准语法:
{{each target}}
{{$index}} {{$value}}
{{/each}}
原始语法:
<% for(var i = 0; i < target.length; i++){ %>
<%= i %> <%= target[i] %>
<% } %>
target 支持 array 与 object 的迭代,其默认值为 $data。
$value 与 $index 可以自定义:{{each target $val $key}}。
{{each user $key $val}}
{{$key}}:{{$val}}
{{/each}}
变量
标准语法:
{{set temp = data.sub.content}}
原始语法:
<% var temp = data.sub.content; %>
npm i express express-art-template -S
const express=require("express");
var app=express();
app.use(express.static("./"));//设置静态目录的
app.engine('art', require('express-art-template'));
app.set("views options",{
debug:process.env.Node_ENV!=='production'
}); 启动模板引擎调试模式。如果为 true: {cache:false, minimize:false, compileDebug:true}
cache 是否开启缓存
minimize 是否开启压缩。它会运行 htmlMinifier,将页面 HTML、CSS、CSS 进行压缩输出
compileDebug 是否编译调试版
compileDebug:true}
//默认文件夹是根目录,这里指定为view文件夹
app.set("views",path.join(__dirname,"./view"));//设置views目录为view文件夹
app.set("view engine","art");//设置view内容默认为art文件,渲染时可以不加扩展名
app.get("/",function(req,res){
res.render("index.art",{data});
})
//app.set("view engine","art"); 这个设置后就可以不用写扩展名
app.get("/",function(req,res){
res.render("index",{data});
})
基础语法与上面相同
标准语法:
{{extend './layout.art'}}
{{block 'head'}} ... {{/block}}
原始语法:
<% extend('./layout.art') %>
<% block('head', function(){ %> ... <% }) %>
模板继承允许你构建一个包含你站点共同元素的基本模板“骨架”。范例:
{{block 'title'}}My Site{{/block}}
{{block 'head'}}
{{/block}}
{{block 'content'}}{{/block}}
{{extend './layout.art'}}
{{block 'title'}}{{title}}{{/block}}
{{block 'head'}}
{{/block}}
{{block 'content'}}
This is just an awesome page.
{{/block}}
var express=require("express");
var app=express();
app.use(express.static("./"));
app.engine('art', require('express-art-template'));
app.get("/",function(req,res){
res.render("./index.art",{title:"xietian"});
})
app.listen(4001);
渲染 index.art 后,将自动应用布局骨架。
标准语法:
{{include './header.art'}}
{{include './header.art' data}}
原始语法:
<% include('./header.art') %>
<% include('./header.art', data) %>
data 数默认值为 $data;标准语法不支持声明 object 与 array,只支持引用变量,而原始语法不受限制。 art-template 内建 HTML 压缩器,请避免书写 HTML 非正常闭合的子模板,否则开启压缩后标签可能会被意外“优化。
注册过滤器
var express=require("express");
var template=require("art-template");
var app=express();
app.use(express.static("./"));
app.engine('art', require('express-art-template'));
template.defaults.imports.randomColor=function(){
var col="#";
for(var i=0;i<6;i++){
col+=Math.floor(Math.random()*16).toString(16);
}
return col;
}
template.defaults.imports.getSum=function(a,b){
return a+b;
}
app.get("/",function(req,res){
res.render("./c.art",{data:"xietian"});
})
app.listen(4001);
过滤器函数第一个参数接受目标值。
标准语法:
{{extend './layout.art'}}
{{block 'title'}}{{data}} site{{/block}}
{{block 'head'}}
{{/block}}
{{block 'content'}}
asdb
{{getSum(5,6)}}
{{/block}}
原始语法:
<%= $imports.dateFormat($imports.getSum(5,6), '') %>
CMS网站发布系统的原型
{{block 'title'}}my site{{/block}}
{{title}}
{{content}}
{{each list}}
- {{$value}}
{{/each}}
var express=require("express");
var path=require("path");
var fs=require("fs");
var template=require("art-template");
var app=express();
app.use(express.static("./"));
app.engine('art', require('express-art-template'));
app.listen(4001);
template.defaults.imports.randomColor=function(){
var col="#";
for(var i=0;i<6;i++){
col+=Math.floor(Math.random()*16).toString(16);
}
return col;
}
var arr=[
{title:"第一页",imgSrc:"./2.jpg",content:"这篇文章的内容是1...",list:[1,2,3,4,5]},
{title:"第二页",imgSrc:"./3.jpg",content:"这篇文章的内容是2...",list:[6,7,8,9]},
{title:"第三页",imgSrc:"./4.jpg",content:"这篇文章的内容是3...",list:[1,2,45,2]},
{title:"第四页",imgSrc:"./5.jpg",content:"这篇文章的内容是4...",list:[23,5,24,5]},
{title:"第五页",imgSrc:"./6.jpg",content:"这篇文章的内容是5...",list:[2,4,5,21]},
]
function saveFile(html,i){
return new Promise(resolve=>{
fs.writeFile(path.join(__dirname,"./public/"+i+".html"),html,"utf8",function(){
resolve();
})
})
}
async function init(){
for(var i=0;i