上一篇咱们使用了CLI的方式测试了SSR,这篇文章来说如何在前文的基础上搭建一个Koa Server,实现真正意义上的SSR。html
demo在这前端
主要内容node
Koa搭建react
完善SSR逻辑webpack
新建server/index.js:git
咱们使用Koa v2.0的版本;github
npm i koa@next -S;
先搭建一个最简单的服务器web
const Koa = require("koa"); const app = new Koa(); app.use(ctx => { ctx.body = 'Hello Koa'; }); app.listen(8088, _ => { console.log('server started') });
添加一个npm scriptnpm
"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" },
执行json
npm run start
这样一个最简单的Koa框架就搭建起来,下面就能够往里面填充东西了。
在添加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
资源能够被正确载入了。
咱们先添加一个API接口,方便模拟Node端的接口调用,在server/index.js增长:
//API接口 router.get('/api/todo_list', async(ctx, 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('/', async(ctx, next) => { let $ = await loadHTMLTemplate(path.resolve(__dirname, '../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框架已经搭建完成,剩下的工做就是结合工做须要,在里面添砖加瓦啦。