从零开始搭建React同构应用四:搭建Koa Server & 完善SSR

从零开始搭建React同构应用(四):搭建Koa Server & 完善SSR

上一篇我们使用了CLI的方式测试了SSR,这篇文章来讲如何在前文的基础上搭建一个Koa Server,实现真正意义上的SSR。

demo在这

主要内容

  1. Koa搭建

  2. 完善SSR逻辑

Koa搭建

新建server/index.js

我们使用Koa v2.0的版本;

npm i koa@next -S;

先搭建一个最简单的服务器

const Koa = require("koa");
const app = new Koa();

app.use(ctx => {
  ctx.body = 'Hello Koa';
});

app.listen(8088,_ => {
    console.log('server started')
});

添加一个npm script

"scripts": {
    "start": "node --harmony server/index",//启动HTTP服务器
    "watch": "webpack -d -w --progress --colors --bs","test-server": "anywhere -p 18341 -d ./build","dist": "cross-env NODE_ENV='production' webpack -p","test-ssr": "node --harmony test/cli.js"
  },

执行

npm run start

这样一个最简单的Koa框架就搭建起来,下面就可以往里面填充东西了。

配置router

在添加router之前,我们需要加载webpack编译生成的HTML模板,这里我们没有使用EJS,HBS等Nodejs渲染引擎,我们而是使用cheerio来帮助我们操作HTML,cheerio可以让我们在Node环境下像使用jQuery一样来操作HTML,非常容易上手,这里是它的API,基本和jQuery无差别。

server/index.js增加:

const cheerio = require("cheerio");
const fs = require("fs");
const path = require("path");
const Promise = require("bluebird");
const serve = require('koa-static-server');
const readFileAsync = Promise.promisify(fs.readFile);

/**
 * 读取HTML模版,返回cheerio实例
 * @param path
 * @return {Promise.<*>}
 */
async function loadHTMLTemplate(path) {
    try {
        let content = await readFileAsync(path);
        return cheerio.load(content);

    } catch (e) {
        console.error(e);
        return false;
    }
}

我们使用koa-better-router中间件作为路由模块。我们添加一个router,在server/index.js增加:

const router = require('koa-better-router')().loadMethods();

router.get('/',async(ctx,next) => {

    let $ = await loadHTMLTemplate(path.resolve(__dirname,'../build/index.html'));

    if (!$) {
        return ctx.body = null;
    }

    return ctx.body = $.html();


});

app.use(router.middleware());

执行

npm run start

我们会发现CSS,JS等文件没有被加载进来,因为没有对应的路由,下面我们配置静态文件服务。

配置静态文件服务

我们不可能为所有的资源都写router,因此我们需要配置一个静态文件服务。这里我使用了koa-static-server中间件。

我们以build目录作为资源文件根目录,在server/index.js增加:

const serve = require('koa-static-server');
const readFileAsync = Promise.promisify(fs.readFile);
const RES_PATH = path.resolve(__dirname,'../build/');

//hfs
app.use(serve({rootDir: RES_PATH}));

执行

npm run start

资源可以被正确载入了。

完善SSR逻辑

我们先添加一个API接口,方便模拟Node端的接口调用,在server/index.js增加:

//API接口
router.get('/api/todo_list',next) => {

    return ctx.body = ['11','222'];

});

我们还是以Index.jsx为例:

test/cli.js中的代码copy过来。修改/路由

const Koa = require("koa");
const app = new Koa();
const router = require('koa-better-router')().loadMethods();
const cheerio = require("cheerio");
const fs = require("fs");
const path = require("path");
const Promise = require("bluebird");
const serve = require('koa-static-server');
const readFileAsync = Promise.promisify(fs.readFile);
const RES_PATH = path.resolve(__dirname,'../build/');
const fetch = require("isomorphic-fetch");

router.get('/','../build/index.html'));

    if (!$) {
        return ctx.body = null;
    }

    let IndexBundle = require("../build_server/index.bundle.js");

    //fetch接口数据
    let todoList = await(await fetch('http://localhost:8088/api/todo_list')).json();

    let initialData = {todoList};

    let instance = React.createElement(IndexBundle.default,initialData);

    let str = renderToString(instance);


    $('#wrap').html(str);

    //前后端数据要同步
    let syncScript = `<script id="server-data">window._SERVER_DATA=${JSON.stringify(initialData)}</script>`;

    $('head').append(syncScript);

    return ctx.body = $.html();


});

这里要注意前后端数据要同步,我把Node端获取的数据放在window._SERVER_DATA中了,前端渲染的时候会优先使用window._SERVER_DATA来渲染。

if (process.browser) {

    //初始数据,用于和server render数据同步
    let initialData = window._SERVER_DATA || {};

    let store = createStore(reducers,initialData,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

    let App = connect(_ => _)(Layout);//用connect包装一下,这里只用到mapStateToProps,而且不对state加以过滤

    ReactDOM.render(
        <Provider store={store}>
            <App/>
        </Provider>,document.getElementById('wrap'));
}

执行

npm run start

访问http://127.0.0.1:8088/

可以看到#wrap中已经被填充渲染好的HTML文本了,Node端和前端的数据也同步了。 ^_^

至此,一个简单的SSR框架已经搭建完成,剩下的工作就是结合工作需要,在里面添砖加瓦啦。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom