Koa实现服务端渲染,是个什么概念

时间:2019-11-14 18:19来源:关于计算机
跌落首屏时间,“直出”是个什么概念? 2015/12/31 · HTML5 · 2评论 ·首屏 最先的作品出处: VaJoy Larn    早些年前端还处于茹毛饮血、JQuery不名一格的不经常,前后端代码的耦合度相当高

跌落首屏时间,“直出”是个什么概念?

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数据卡塔 尔(英语:State of Qatar)。

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

图片 5

直出和同构

直出大约其实正是“服务端渲染并出口”,跟发轫大家谈到的左右端不分相互的支出情势为主相近,只是后端语言我们换到了 node 。

09年始发冒头的 node 现在成了当红炸子鸡,包涵Ali、Tencent在内的各大集团都布满地把 node 用到品种上,前后端整而为黄金时代,倘若 node 的特性适用于您的项目,那么何乐不为呢。

我们在此边也提起了三个“同构”的定义,即上下端(这里的“后端”指的是直出端,数据接口不确定由node开采卡塔 尔(英语:State of Qatar)应用同黄金年代套代码方案,方便维护。

近日 node 在服务端有着许多主流抑或肥猪流的框架,包蕴express、koa、thinkjs 等,能够异常的快上手,利用各类中间件得以进行快速开垦。

别的诸如 ejs、jade 那样的渲染模板能让我们轻巧地把首屏内容(数据或渲染好的DOM树卡塔 尔(阿拉伯语:قطر‎注入页面中。

如此那般客商访谈到的就是大器晚成度包罗首屏内容的页面,大大裁减了等待时间,升高了心得。

图片 6

示例

在这里地大家以 koa + ejs + React 的服务端渲染为例,来拜谒二个简洁明了的“直出”方案是什么落实的。该示例也能够在我的github内外载到。

品种的目录结构如下:

+---data //模拟数据接口,放了三个.json文件 +---dist //文件营造后(gulp/webpack卡塔 尔(英语:State of Qatar)寄放处 | +---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 拆解拆解分析处理

node 端是不会友善分辨 React 的 jsx 语法的,故大家须求在等级次序文件中引进 node-jsx ,就算前几天得以设置 babel-cli 后(并丰盛预设卡塔尔国接收 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_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);

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) ,我们还传出了 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 的服务端渲染管理豆蔻梢头体化支付是没难点的,即是支付体验缺乏好,主因只怕各个地区面临es二零一五 辅助不完了招致的。

虽说在服务端运营前,我们在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

React是现阶段前端社区最流行的UI库之黄金年代,它的依附组件化的开拓格局十分的大地提高了前端开辟体验,React通过拆分一个大的运用至二个个小的零器件,来驱动我们的代码越来越可被选定,以致得到更加好的可维护性,等等还会有任何众多的优点...

透过React, 大家日常会付出一个单页应用(SPA卡塔 尔(英语:State of Qatar),单页应用在浏览器端会比古板的网页有更加好的客商体验,浏览器经常会获得贰个body为空的html,然后加载script内定的js, 当全部js加载完结后,开头实行js, 最终再渲染到dom中, 在这里个进度中,日常顾客只好等待,什么都做不了,要是客商在一个急速的互联网中,高配置的设备中,以上先要加载全体的js然后再实行的进程只怕不是何等大主题素材,然则有成都百货上千状态是我们的网速平日,设备也只怕不是最棒的,在此种景观下的单页应用也许对客户来讲是个相当差的顾客体验,客商可能尚未体验到浏览器端SPA的裨益时,就早就偏离网址了,那样的话你的网站做的再好也不会有太多的浏览量。

只是大家总无法回到原先的二个页面叁个页面包车型客车思想支付吧,今世化的UI库都提供了服务端渲染(SSQX56)的效果,使得大家付出的SPA应用也能周密的周转在服务端,大大加快了首屏渲染的小时,这样的话客商不只能越来越快的看看网页的内容,与此同一时间,浏览器同偶尔候加载供给的js,加载完后把具备的dom事件,及各样相互影响增添到页面中,最终还是以二个SPA的款型运行,那样的话大家既升高了首屏渲染的光阴,又能得到SPA的顾客端客商体验,对于SEO也是个必需的作用。

OK,大家大概通晓了SSHaval的供给性,下边大家就能够在二个React App中来完毕服务端渲染的效应,BTW, 既然我们已经处于二个所在是async/await的条件中,这里的服务端大家应用koa2来落成大家的服务端渲染。

初叶化一个平日的单页应用SPA

先是我们先不管服务端渲染的事物,我们先创立三个基于React和React-Router的SPA,等大家把一个完整的SPA创制好后,再踏向SS路虎极光的效应来最大化提高app的习性。

第豆蔻梢头踏向app入口 App.js:

import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route } from 'react-router-dom';

const Home = () => <div>Home</div>;
const Hello = () => <div>Hello</div>;

const App = () => {
 return (
  <Router>
   <Route exact path="/" component={Home} />
   <Route exact path="/hello" component={Hello} />
  </Router>
 )
}

ReactDOM.render(<App/>, document.getElementById('app'))

地方我们为路由/ 和 /hello成立了2个只是渲染一些文字到页面包车型客车构件。但当大家的门类变得更为大,组件越来越多,最后大家打包出来的js也许会变得非常的大,以至变得不可控,所以啊我们率先步需求优化的是代码拆分(code-splitting卡塔 尔(阿拉伯语:قطر‎,幸运的是透过webpack dynamic import和 react-loadable,大家能够比较轻易酿成那点。

用React-Loadable来时间代码拆分

选取在此以前,先安装 react-loadable:

npm install react-loadable
# or
yarn add react-loadable

然后在您的 javascript中:

//...
import Loadable from 'react-loadable';
//...

const AsyncHello = Loadable({
 loading: <div>loading...</div>,
 //把你的Hello组件写到单独的文件中
 //然后使用webpack的 dynamic import
 loader: () => import('./Hello'), 
})

//然后在你的路由中使用loadable包装过的组件:
<Route exact path="/hello" component={AsyncHello} />

很粗大略吗,大家只需求importreact-loadable, 然后传一些option进去就能够了,个中的loading选拔是当动态加载Hello组件所需的js时,渲染loading组件,给客商风流倜傥种加载中的认为,体验也会比什么都不加好。

好了,以后少年老成经大家访谈首页的话,唯有Home组件信任的js才会被加载,然后点击有些链接步向hello页面包车型客车话,会先渲染loading组件,并还要异步加载hello组件信任的js,加载完后,替换掉loading来渲染hello组件。通过依据路由拆分代码到分裂的代码块,咱们的SPA已经有了相当的大的优化,cheers。更叼的是react-loadable平等支撑SS雷克萨斯LC,所以您能够在随心所欲地点使用react-loadable,不管是运作在前面三个照旧服务端,要让react-loadable在服务端平常运转的话我们须要做一些附加的布置,本文前边会讲到,先不急。‍

到此处大家早已成立好叁在那之中心的React SPA,加上代码拆分,我们的app已经有了不利的习性,但是大家还是能够更进一层十二万分的优化app的属性,上面大家因而增添SSPRADO的职能来更为进步加载速度,顺便消释一下SPA中的SEO难点。

加盟服务端渲染(SS安德拉卡塔尔功用

首先大家先搭建二个最简易的koa web服务器:

npm install koa koa-router

下一场在koa的输入文件app.js中:

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();
router.get('*', async (ctx) => {
 ctx.body = `
   <!DOCTYPE html>
    <html lang="en">
    <head>
     <meta charset="UTF-8">
     <title>React SSR</title>
    </head>
    <body>
     <div id="app"></div>
     <script type="text/javascript" src="/bundle.js"></script>
    </body>
   </html>
  `;
});

app.use(router.routes());
app.listen(3000, '0.0.0.0');

上面*路由代表专擅的url进来大家都暗许渲染那几个html,蕴涵html中封装出来的js,你也能够用部分服务端模板引擎(如:nunjucks)来一向渲染html文件,在webpack打包时经过html-webpack-plugin来机关插入打包出来的js/css财富路线。

OK, 我们的简约koa server好了,接下去大家早前编写制定React SS奥迪Q3的输入文件AppSSR.js,这里大家供给使用StaticRouter来替代早前的BrowserRouter,因为在服务端,路由是静态的,用BrowserRouter的话是不起效率的,后边还有恐怕会做一些铺排来驱动react-loadable运作在服务端。

升迁: 你能够把任何node端的代码用ES6/JSX风格编写,并不是意气风发对commonjs,部分JSX, 但那样的话你需求用webpack把全路服务端的代码编写翻译成commonjs风格,本领使得它运维在node情况中,这里的话大家把React SSEvoque的代码单独抽取去,然后在平日的node代码里去require它。因为大概在三个存活的门类中,早先都以commonjs的品格,把原先的node代码二回性转成ES6的话开支有一些大,可是能够中期一步步的再迁移过去

OK, 今后在你的 AppS奥迪Q5Evoque.js中:

import React from 'react';
//使用静态 static router
import { StaticRouter } from 'react-router-dom';
import ReactDOMServer from 'react-dom/server';
import Loadable from 'react-loadable';
//下面这个是需要让react-loadable在服务端可运行需要的,下面会讲到
import { getBundles } from 'react-loadable/webpack';
import stats from '../build/react-loadable.json';

//这里吧react-router的路由设置抽出去,使得在浏览器跟服务端可以共用
//下面也会讲到...
import AppRoutes from 'src/AppRoutes';

//这里我们创建一个简单的class,暴露一些方法出去,然后在koa路由里去调用来实现服务端渲染
class SSR {
 //koa 路由里会调用这个方法
 render(url, data) {
  let modules = [];
  const context = {};
  const html = ReactDOMServer.renderToString(
   <Loadable.Capture report={moduleName => modules.push(moduleName)}>
    <StaticRouter location={url} context={context}>
     <AppRoutes initialData={data} />
    </StaticRouter>
   </Loadable.Capture>
  );
  //获取服务端已经渲染好的组件数组
  let bundles = getBundles(stats, modules);
  return {
   html,
   scripts: this.generateBundleScripts(bundles),
  };
 }
 //把SSR过的组件都转成script标签扔到html里
 generateBundleScripts(bundles) {
  return bundles.filter(bundle => bundle.file.endsWith('.js')).map(bundle => {
   return `<script type="text/javascript" src="${bundle.file}"></script>n`;
  });
 }

 static preloadAll() {
  return Loadable.preloadAll();
 }
}

export default SSR;

当编写翻译那些文件的时候,在webpack安顿里使用target: "node"externals,何况在您的包装前端app的webpack配置中,供给踏入react-loadable的插件,app的打包供给在ssr打包在此以前运维,否则拿不到react-loadable须要的各组件新闻,先来看app的打包:

//webpack.config.dev.js, app bundle
const ReactLoadablePlugin = require('react-loadable/webpack')
 .ReactLoadablePlugin;

module.exports = {
 //...
 plugins: [
  //...
  new ReactLoadablePlugin({ filename: './build/react-loadable.json', }),
 ]
}

在.babelrc中加入loadable plugin:

{
 "plugins": [
   "syntax-dynamic-import",
   "react-loadable/babel",
   ["import-inspector", {
    "serverSideRequirePath": true
   }]
  ]
}

地方的布局会让react-loadable略知生机勃勃二怎么组件最终在服务端被渲染了,然后直接插入到html script标签中,并在前面三个伊始化时把SS奥迪Q5过的构件酌量在内,防止重复加载,下边是SS昂科拉的打包:

//webpack.ssr.js
const nodeExternals = require('webpack-node-externals');

module.exports = {
 //...
 target: 'node',
 output: {
  path: 'build/node',
  filename: 'ssr.js',
  libraryExport: 'default',
  libraryTarget: 'commonjs2',
 },
 //避免把node_modules里的库都打包进去,此ssr js会直接运行在node端,
 //所以不需要打包进最终的文件中,运行时会自动从node_modules里加载
 externals: [nodeExternals()],
 //...
}

然后在koa app.js, require它,况兼调用SS奇骏的办法:

//...koa app.js
//build出来的ssr.js
const SSR = require('./build/node/ssr');
//preload all components on server side, 服务端没有动态加载各个组件,提前先加载好
SSR.preloadAll();

//实例化一个SSR对象
const s = new SSR();

router.get('*', async (ctx) => {
 //根据路由,渲染不同的页面组件
 const rendered = s.render(ctx.url);

 const html = `
  <!DOCTYPE html>
   <html lang="en">
   <head>
    <meta charset="UTF-8">
   </head>
   <body>
    <div id="app">${rendered.html}</div>
    <script type="text/javascript" src="/runtime.js"></script>
    ${rendered.scripts.join()}
    <script type="text/javascript" src="/app.js"></script>
   </body>
  </html>
 `;
 ctx.body = html;
});
//...

上述是个简单的兑现React SSRAV4到koa web server, 为了使react-loadable通晓怎么着组件在服务端渲染了,rendered里面的scripts数组里面包蕴了SSSportage过的构件组成的逐少年老成script标签,里面调用了SSR#generateBundleScripts()办法,在插入时索要确定保证那个script标签在runtime.js之后((通过 CommonsChunkPlugin来抽出来)),而且在app bundle以前(也正是初步化的时候应该已经掌握后面包车型客车如何组件已经渲染过了卡塔 尔(英语:State of Qatar)。愈来愈多react-loadable服务端帮忙,参谋这里.

地方大家还把react-router的路由都单身抽取去了,使得它能够运维在浏览器跟服务端,以下是AppRoutes组件:

//AppRoutes.js
import Loadable from 'react-loadable';
//...

const AsyncHello = Loadable({
 loading: <div>loading...</div>,
 loader: () => import('./Hello'), 
})

function AppRoutes(props) {
 <Switch>
  <Route exact path="/hello" component={AsyncHello} />
  <Route path="/" component={Home} />
 </Switch> 
}

export default AppRoutes

//然后在 App.js 入口中
import AppRoutes from './AppRoutes';
// ...
export default () => {
 return (
  <Router>
   <AppRoutes/>
  </Router>
 )
}

服务端渲染的始发状态

近期停止,大家早已创办了叁个React SPA,并且能在浏览器端跟服务端协同运营,社区称之为universal app或者 isomophic app。可是大家现在的app还恐怕有两个遗留难点,平时的话大家app的数量依旧状态都亟待通过远端的api来异步获取,获得数码后我们才干最初渲染组件,服务端SS福睿斯也是同风度翩翩,大家要动态的获取早先数据,然后工夫扔给React去做SS纳瓦拉,然后在浏览器端大家还要最初化就能够一齐获取那几个SSHighlander时的起头化数据,幸免浏览器端最先化时又再次获得了贰回。

上边大家大致从github获取一些类别的消息作为页面领头化的数量, 在koa的app.js中:

//...
const fetch = require('isomorphic-fetch');

router.get('*', async (ctx) => {
 //fetch branch info from github
 const api = 'https://api.github.com/repos/jasonboy/wechat-jssdk/branches';
 const data = await fetch(api).then(res => res.json());

 //传入初始化数据
 const rendered = s.render(ctx.url, data);

 const html = `
  <!DOCTYPE html>
   <html lang="en">
   <head>
    <meta charset="UTF-8">
   </head>
   <body>
    <div id="app">${rendered.html}</div>

    <script type="text/javascript">window.__INITIAL_DATA__ = ${JSON.stringify(data)}</script>

    <script type="text/javascript" src="/runtime.js"></script>
    ${rendered.scripts.join()}
    <script type="text/javascript" src="/app.js"></script>
   </body>
  </html>
 `;
 ctx.body = html;
});

下一场在你的Hello组件中,你需要checkwindow内部(大概在App入口中联合判定,然后经过props传到子组件中卡塔 尔(英语:State of Qatar)是还是不是存在window.__INITIAL_DATA__,局地话平素用来作为开首数据,未有的话大家在componentDidMount生命周期函数中再去来多少:

export default class Hello extends React.Component {
 constructor(props) {
  super(props);

  this.state = {
   //这里直接判断window,如果是父组件传入的话,通过props判断
   github: window.__INITIAL_DATA__ || [],
  };
 }

 componentDidMount() {
  //判断没有数据的话,再去请求数据
  //请求数据的方法也可以抽出去,以让浏览器及服务端能统一调用,避免重复写
  if (this.state.github.length <= 0) {
   fetch('https://api.github.com/repos/jasonboy/wechat-jssdk/branches')
    .then(res => res.json())
    .then(data => {
     this.setState({ github: data });
    });
  }
 }

 render() {
  return (
   <div>
    <ul>
     {this.state.github.map(b => {
      return <li key={b.name}>{b.name}</li>;
     })}
    </ul>
   </div>
  );
 }
}

好了,以往要是页面被服务端渲染过的话,浏览器会获得具有渲染过的html, 包罗开端化数据,然后通过这一个SS奥迪Q5的剧情十一分加载的js,再组成一个总体的SPA,就好像一个平凡的SPA同样,可是大家获得了越来越好的品质,越来越好的SEO。

React-v16 更新

在React的风行版v16中,SS普拉多的API做了成都百货上千的优化,並且提供了新的基于流的API来更加好的进级换代质量,通过streaming api, 服务端能够边渲染边把前边渲染好的html发到浏览器,浏览器端也得以提前起头渲染页面并非等服务端全数组件都渲染实现后技艺最早浏览器端的先河化,进步了质量也下落了服务端财富的消耗。还应该有八个在浏览器端必要留意的是内需动用ReactDOM.hydrate()来代替以前的ReactDOM.render(),更加的多的更新参照他事他说加以调查medium作品whats-new-with-server-side-rendering-in-react-16.

要翻开完整的demo, 参谋koa-web-kit, koa-web-kit是叁个今世化的依靠React/Koa的全栈开拓框架,包蕴React SS奇骏帮忙,可以直接用来测量检验服务端渲染的效果与利益

结论

好了,以上便是React-SSLAND + Koa的简洁明了推行,通过SS汉兰达,大家既进步了品质,又很好的满意了SEO的渴求,贝斯特of the Both Worlds。

English Version

上述就是本文的全体内容,希望对我们的读书抱有利于,也盼望大家多多照料脚本之家。

你或者感兴趣的篇章:

  • 精解React项目的服务端渲染改变(koa2+webpack3.11)

编辑:关于计算机 本文来源:Koa实现服务端渲染,是个什么概念

关键词: