一个web页面文件的代码可能是这样的

减弱首屏时间,“直出”是个如何概念?

2015/12/31 · HTML5 · 2
评论 ·
首屏

初稿出处: VaJoy Larn   

早几年前端还处在刀耕火种、JQuery自成一家的时期,前后端代码的耦合度极高,三个web页面文件的代码恐怕是这么的:

图片 1

图片 2

那意味着后端的程序猿往往得承受一部分改造HTML、编写脚本的做事,而后边三个开荒者也得询问页面上存在的服务端代码含义。

有的时候某处页面逻辑的改观,鉴于代码的混合着去搭配,可能都不明确相应请后端仍然前面一个来退换(就算他们都能处理)。

图片 3

后边三个框架热潮

有句俗话说的好——“人啊,假诺擅于开口‘关小编屁事’和‘关你屁事’这俩句,能够节约人生中的超过五成光阴”。

乘势那七年被 angular
牵头带起的各类前端MV*框架的流行,后端能够毋须再于静态页面成本心绪,只须要心驰神往开采数据接口供前端选择就可以。得益于此,前后端终于能够欣慰地相互道一声“关笔者屁事”或“关你屁事”了。

以 avalon
为例,前端只要求在页面加载时发送个ajax央浼取得数据绑定到vm,然后做view层渲染就可以:

var vm = avalon.define({ $id: “wrap”, list: [] });
fetch(‘data/list.php’) //向后端接口发出央浼 .then(res => res.json())
.then(json => { vm.list = json; //数据注入vm avalon.scan();
//渲染view层 });

1
2
3
4
5
6
7
8
9
10
11
var vm = avalon.define({
    $id: "wrap",
    list: []
});
 
fetch(‘data/list.php’)   //向后端接口发出请求
    .then(res => res.json())
    .then(json => {
        vm.list = json; //数据注入vm
        avalon.scan();  //渲染view层
    });

静态页面的代码也由前端一手明白,原来服务端的代码换到了 avalaon
的专项使用属性与插值表明式:

ul ms-controller=”wrap”> li ms-repeat=”list”>{el.name}li>
ul>

1
2
3
ul ms-controller="wrap">
    li ms-repeat="list">{el.name}li>
ul>

内外端代码隔断的花样大大晋级了类其他可维护性和开荒成效,已经产生一种web开拓的主流格局。它解放了后端程序猿的双臂,也将更加多的调节权转移给前端职员(当然前端也因此须要多读书一些框架知识)。

图片 4

弊端

上下端隔断的情势就算给支付拉动了造福,但相比较一碗水端平的旧格局,页面首屏的数量需求在加载的时候向服务端发去央浼本事赢得,多了请求等候的岁月(RTT)。

那意味着用户访谈页面包车型大巴时候,这段“等待后端再次回到数据”的时延会处在白屏状态,假若用户网速差,那么这段首屏等候时间会是相当倒霉的体会。

本来拉到数据后,还得做 view
层渲染(客户端引擎的管理还是一点也不慢的,忽略渲染的时日),那又凭仗于框架自己,即框架要先被下载下来手艺管理这么些视图渲染操作。那么好东西,多少个angular.min.js 就高达了 120
多KB,用着渣连续信号的用户得多等上一两秒来下载它。

这么看来,单纯前后端隔开的样式存在首屏时间较长的难题,除非现在平均网速达到上G/s,不然都以不赏心悦指标体会。

另外利用前端框架的页面也不实惠SEO,其实应该说不便民国时代内那几个渣寻觅引擎的SEO,Google现已能从内部存储器中去抓数据(客户端渲染后的DOM数据)。

so 如何做?相信广大对象猜到了——用 node 来助阵。

图片 5

直出和同构

直出大约其实正是“服务端渲染并出口”,跟起头大家提起的内外端不分轩轾的费用形式为主类似,只是后端语言大家换到了
node 。

09年启幕冒头的 node
今后成了当红炸子鸡,包罗Ali、Tencent在内的各大商厦都遍布地把 node
用到项目上,前后端整而为一,假诺 node
的性状适用于您的项目,那么何乐而不为呢。

咱俩在那边也谈到了二个“同构”的定义,即上下端(这里的“后端”指的是直出端,数据接口不自然由node开采)选择一样套代码方案,方便维护。

最近 node 在服务端有着众多主流抑或非主流的框架,包括express、koa、thinkjs 等,能够极快上手,利用各样中间件得以开始展览高效开辟。

除此以外诸如 ejs、jade
那样的渲染模板能让我们轻易地把首屏内容(数据或渲染好的DOM树)注入页面中。

一个web页面文件的代码可能是这样的。那般用户访谈到的就是现已包蕴首屏内容的页面,大大收缩了等候时间,进步了感受。

图片 6

示例

在此地大家以 koa + ejs + React
的服务端渲染为例,来探望二个简易的“直出”方案是何等实现的。该示例也能够在我的github内外载到。

品种的目录结构如下:

+—data //模拟数据接口,放了七个.json文件 +—dist
//文件构建后(gulp/webpack)存放处 | +—css | | +—common | | —page
| +—js | | +—component | | —page | —views | +—common | —home
+—modules //一些自行封装的通用业务模块 +—routes //路由陈设 —src
//未创设的文本夹 +—css | +—common | +—component | —page +—js |
+—component //React组件 | —page //页面入口文件 —views //ejs模板
+—common —home

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
+—data   //模拟数据接口,放了一个.json文件
+—dist  //文件构建后(gulp/webpack)存放处
|   +—css
|   |   +—common
|   |   —page
|   +—js
|   |   +—component
|   |   —page
|   —views
|       +—common
|       —home
+—modules  //一些自行封装的通用业务模块
+—routes  //路由配置
—src  //未构建的文件夹
    +—css
    |   +—common
    |   +—component
    |   —page
    +—js
    |   +—component //React组件
    |   —page //页面入口文件
    —views  //ejs模板
        +—common
        —home

1. node 端 jsx 剖析管理

一个web页面文件的代码可能是这样的。node 端是不会融洽辨认 React 的 jsx
语法的,故大家须求在品种文件中引进 node-jsx ,尽管明天得以设置 babel-cli
一个web页面文件的代码可能是这样的。(并丰硕预设)应用 babel-node 命令代替node,但后面一个用起来总会出标题,故权且依然选拔 node-jsx 方案:

//app.js require(‘node-jsx’).install({ //让node端能深入分析jsx extension:
‘.js’ }); var fs = require(‘fs’), koa = require(‘koa’), compress =
require(‘koa-compress’), render = require(‘koa-ejs’), mime =
require(‘mime-types’), r_home = require(‘./routes/home’), limit =
require(‘koa-better-ratelimit’), getData = require(‘./modules/getData’);
var app = koa(); app.use(limit({ duration: 1000*10 , max: 500,
accessLimited : “您的伸手太过数次,请稍后重试”}) ); app.use(compress({
threshold: 50, flush: require(‘zlib’).Z_一个web页面文件的代码可能是这样的。SYNC_FLUSH })); render(app, {
//ejs渲染配置 root: ‘./dist/views’, layout: false , viewExt: ‘ejs’,
cache: false, debug: true }); getData(app); //首页路由 r_一个web页面文件的代码可能是这样的。home(app);
app.use(function*(next){ var p = this.path; this.type = mime.lookup(p);
this.body = fs.createReadStream(‘.’+p); }); app.listen(3300);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//app.js
require(‘node-jsx’).install({  //让node端能解析jsx
    extension: ‘.js’
});
 
var fs = require(‘fs’),
    koa = require(‘koa’),
    compress = require(‘koa-compress’),
    render = require(‘koa-ejs’),
    mime = require(‘mime-types’),
    r_home = require(‘./routes/home’),
    limit = require(‘koa-better-ratelimit’),
    getData = require(‘./modules/getData’);
 
var app = koa();
 
app.use(limit({ duration: 1000*10 ,
    max: 500, accessLimited : "您的请求太过频繁,请稍后重试"})
);
app.use(compress({
    threshold: 50,
    flush: require(‘zlib’).Z_SYNC_FLUSH
}));
 
render(app, {  //ejs渲染配置
    root: ‘./dist/views’,
    layout: false ,
    viewExt: ‘ejs’,
    cache: false,
    debug: true
});
 
getData(app);
 
//首页路由
r_home(app);
 
app.use(function*(next){
    var p = this.path;
    this.type = mime.lookup(p);
    this.body = fs.createReadStream(‘.’+p);
});
 
app.listen(3300);

2. 首页路由(’./routes/home’)配置

var router = require(‘koa-router’), getHost =
require(‘../modules/getHost’), apiRouter = new router(); var React =
require(‘react/lib/ReactElement’), ReactDOMServer =
require(‘react-dom/server’); var List =
React.createFactory(require(‘../dist/js/component/List’));
module.exports = function (app) { var data =
this.getDataSync(‘../data/names.json’), //取首屏数据 json =
JSON.parse(data); var lis = json.map(function(item, i){ return (
<li>{item.name}</li> ) }), props = {color: ‘red’};
apiRouter.get(‘/’, function *() { //首页 yield
this.render(‘home/index’, { title: “serverRender”, syncData: { names:
json, //将取到的首屏数据注入ejs模板 props: props }, reactHtml:
ReactDOMServer.renderToString(List(props, lis)), dirpath: getHost(this)
}); }); app.use(apiRouter.routes()); };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var router = require(‘koa-router’),
    getHost = require(‘../modules/getHost’),
    apiRouter = new router();
 
var React = require(‘react/lib/ReactElement’),
    ReactDOMServer = require(‘react-dom/server’);
var List = React.createFactory(require(‘../dist/js/component/List’));
 
module.exports = function (app) {
 
    var data = this.getDataSync(‘../data/names.json’),  //取首屏数据
        json = JSON.parse(data);
 
    var lis = json.map(function(item, i){
       return (
           <li>{item.name}</li>
       )
    }),
        props = {color: ‘red’};
 
    apiRouter.get(‘/’, function *() {  //首页
        yield this.render(‘home/index’, {
            title: "serverRender",
            syncData: {
                names: json,  //将取到的首屏数据注入ejs模板
                props: props
            },
            reactHtml:  ReactDOMServer.renderToString(List(props, lis)),
            dirpath: getHost(this)
        });
    });
 
    app.use(apiRouter.routes());
 
};

只顾这里我们利用了 ReactDOMServer.renderToString 来渲染 React 组件为纯
HTML 字符串,注意 List(props, lis)一个web页面文件的代码可能是这样的。 ,大家还流传了 props 和 children。

其在 ejs 模板中的应用为:

div class=”wrap” id=”wrap”>-reactHtml%>div>

1
div class="wrap" id="wrap">-reactHtml%>div>

就那样轻松地产生了服务端渲染的管理,但还恐怕有一处难题,假设组件中绑定了平地风波,客户端不会感知。

所以在客户端大家也需求再做三次与服务端一致的渲染操作,鉴于服务端生成的DOM会被打上
data-react-id 标识,故在客户端渲染的话,react
会通过该标记位的对待来制止冗余的render,并绑定上相应的事件。

那也是大家把所要注入组件中的数据(syncData)传入 ejs
的缘由,我们将把它看成客户端的叁个全局变量来使用,方便客户端挂载组件的时候用上:

ejs上注入直出多少:

script> syncData = JSON.parse(”); script>

1
2
3
  script>
    syncData = JSON.parse(”);
  script>

页面入口文件(js/page/home.js)挂载组件:

import React from ‘react’; import ReactDOM from ‘react-dom’; var List =
require(‘../component/List’); var lis =
syncData.names.map(function(item, i){ return (
<li>{item.name}</li> ) }); ReactDOM.render( <List
{…syncData.props}> {lis} </List>,
document.getElementById(‘wrap’) );

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from ‘react’;
import ReactDOM from ‘react-dom’;
var List = require(‘../component/List’);
 
var lis = syncData.names.map(function(item, i){  
    return (
        <li>{item.name}</li>
    )
});
ReactDOM.render(
    <List {…syncData.props}>
        {lis}
    </List>,
    document.getElementById(‘wrap’)
);

3. 支持理工科程师具

为了玩鲜,在局地模块里写了 es二〇一四 的语法,然后利用 babel
来做转变管理,在 gulp 和 webpack 中都有应用到,具体可参谋它们的布署。

别的是因为服务端对 es二零一五 的表征集和补充助不完全,合作 babel-core/register
或许使用 babel-node
命令都存在包容难点,故针对具备要求在服务端引进到的模块(举个例子React组件),在koa运转前先做gulp管理转为es5(这么些塑造立模型块仅在服务端会用到,客户端走webpack直接援用未退换模块就可以)。

ejs文件中样式或脚本的内联管理本身动用了和谐支付的
gulp-embed
,风乐趣的恋人能够玩一玩。

4. issue

说实话 React
的服务端渲染管理一体化支付是没难题的,就是付出体验远远不够好,首要缘由依旧各地方对
es2015 帮助不到位导致的。

虽说在服务端运转前,大家在gulp中利用babel对相关模块进行转变,但像 export
default XXX

那样的语法调换后要么不能棉被和衣服务端扶助,只可以降级写为 module.exports =
XXX
。但这么写,在任何模块就没有办法 import XXX from ‘X’ 了(改为
require(‘X’)代替)
,同理可得不爽直。只好期待后续 node(其实应该说V8)
再迭代一些版本能更加好地支撑 es2014 的性状。

其余借使 React 组件涉及列表项,常规我们会增进 key
的props特性来升高渲染功效,但哪怕前后端传入同样的key值,最后 React
渲染出来的 key 值是不均等的,会变成客户端挂载组件时再做一回渲染管理。

对此那一点笔者个人提议是,如若是静态的列表,那么统一都不加 key
,假诺是动态的,那么就加吧,客户端再渲染二遍以为也没多大点事。(恐怕你有越来越好方案请留言哈~)

5. 其它

偶然服务端引进的模块里面,某个东西是只是供给在客户端应用到的,我们以这些示例中的组件component/List为例,里面包车型大巴样式文件

require(‘css/component/List’);

1
require(‘css/component/List’);

不应该在服务端实践的时候利用到,但出于同构,前后端用的一套东西,那么些怎么消除吗?其实很好办,通过
window
对象来判别就能够(只要未有啥中间件给您在服务端也加了window接口)

var isNode = typeof window === ‘undefined’; if(!isNode){
require(‘css/component/List’); }

1
2
3
4
5
var isNode = typeof window === ‘undefined’;
 
if(!isNode){
    require(‘css/component/List’);
}

而是请细心,这里小编透过 webpack
把组件的体裁也打包进了客户端的页面入口文件,其实不服帖。因为经过直出,页面在响应的时候就早就把组件的DOM树都先出示出来了,但以此时候是还尚无取到样式的(样式打包到进口脚本了),须求等到进口脚本加载的时候手艺看到科学的体裁,这一个进度会有三个眨眼的进程,是种不痛快的经验。

进而走直出的话,建议把首屏的体裁抽离出来内联到头顶去。

1 赞 2 收藏 2
评论

图片 7

You may also like...

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图