Medium isomorphic

React & NodeJs изоморфное приложение

Что такое Изоморфный/Универсальный JavaScript?

Это слово стало модным еще в 2015 году.

В общих чертах - это код, который работает как на клиенте, так и сервере.

В чем смысл такого подхода?

Когда пользователь впервые попадает на страницу, все содержимое страницы должно загрузиться, а в современных SPA приложениях это может занять довольно долгое время, а большинство пользователей на сегодняшний день ожидают время загрузки менее чем за две секунды. Во время загрузки содержимого, такая страница пуста или (в лучшем случае) показан прелоадер.

Пожалуй, самой важной проблемой современных SPA приложений является то, что такие страницы не индексируются поисковыми системами.

А вот когда JavaScript отрабатывает на стороне сервера, эти проблемы решаемы.

Преимущества Изоморфного Javascript:

Одним из преимуществ данного подхода к построению приложений является то, что вы получаете преимущества скорости отдачи от сервера готового контента, а после загрузки страницы отработает, клиентский javascript.

  • Единый код для frontend и backend;
  • Легче поддерживать такой код;
  • Индексация поисковиками;
  • Это крутой и современный подход к разработке :).

Для примера я сделал живой пример изоморфного JS приложения, исходный код которого можно найти здесь: https://github.com/mdenisov/isomorphic-react

В пример используется компонент griddle react, чтобы показать, как можно разрабатывать приложения с большими количеством данных, индексируемыми поисковыми системами.

Создание изоморфного приложения

Express является одним из самых популярных веб-серверов Node.js. Он отлично подходит для разработки изоморфного приложения на React.

Итак, давайте разберемся как это все работает.

/server.js

В /server.js настраиваем окружение (веб-сервер, роуты, статику) устанавливаем JSX transpiler:

  var express = require('express'),
path = require('path'),
app = express(),
port = 8000,
bodyParser = require('body-parser');

// Make sure to include the JSX transpiler
require('node-jsx').install();

// Include static assets. Not advised for production
app.use(express.static(path.join(__dirname, 'public')));
// Set view path
app.set('views', path.join(__dirname, 'views'));
// set up ejs for templating. You can use whatever
app.set('view engine', 'ejs');

// Set up Routes for the application
require('./app/routes/coreRoutes.js')(app);

//Route not found -- Set 404
app.get('*', function(req, res) {
    res.json({
        'route': 'Sorry this page does not exist!'
    });
});

app.listen(port);
console.log('Server is Up and Running at Port : ' + port);

Создадим главный файл React-приложения main.js.

/app/main.js

  var React = require('react');
var ReactDOM = require('react-dom');
var ReactApp = require('./components/ReactApp');

var mountNode = document.getElementById('react-main-mount');

ReactDOM.render(
  <ReactApp />,
  mountNode
);

Так как мы собираемся использовать компоненты, и на сервере, и на клиенте, они должны быть оформлены как CommonJS модули (Node friendly). Для клиента такие модули обрабатываются и приводятся к обычному виду, который понимает браузер, в нашем случае - это Browserify, но можно воспользоваться Webpack.

Чтобы произошло волшебство, нужно настроить серверные маршруты с использованием ReactDOMServer.renderToString. Этот метод рендерит компонент (и все вложенные в него компоненты) и просто возвращает HTML строку, которую мы передаем на клиент:

/app/routes/coreRoutes.js

  var React = require('react');
var ReactDOMServer = require('react-dom/server');
var ReactApp = require('../components/ReactApp');

module.exports = function(app) {

  app.get('/', function(req, res){
    // React.renderToString takes your component
    // and generates the markup
    var reactHtml = ReactDOMServer.renderToString(<ReactApp />);
    // Output html rendered by react
    // console.log(myAppHtml);
    res.render('index.ejs', {reactOutput: reactHtml});
  });

};

Как я уже говорил выше, для примера мы построим список с данными. Данные представлены в JSON формате.

/app/components/ReactApp.js

  var React = require('react');

/* create factory with griddle component */
var Griddle = require('griddle-react');

var fakeData = require('../data/fakeData.js').fakeData;
var columnMeta = require('../data/columnMeta.js').columnMeta;
var resultsPerPage = 200;

var ReactApp = React.createClass({
  componentDidMount: function () {
    console.log(fakeData);
  },
  render: function () {
    return (
      <div id="table-area">

      <Griddle
        results={fakeData}
        columnMetadata={columnMeta}
        resultsPerPage={resultsPerPage}
        tableClassName="table"
        />

      </div>
    )
  }
});

/* Module.exports instead of normal dom mounting */
module.exports = ReactApp;

И, наконец, выводим переменную reactOutput в шаблоне:

/views/index.ejs

  <!doctype html>
<html>
  <head>
    <title>React Isomorphic Server Side Rendering Example</title>
    <link href='/styles.css' rel="stylesheet">
  </head>
  <body>
    <h1 id="main-title">React Isomorphic Server Side Rendering Example</h1>
    <!-- reactOutput is the server compiled React Dom Nodes -->
    <!-- comment out reactOutput to see empty non indexable source in browser -->
    <div id="react-main-mount">
      <div><%- reactOutput %></div>
    </div>

    <!-- comment out main.js to ONLY see server side rendering -->
    <script src="/main.js"></script>

  </body>
</html>

Вот и все! Теперь у нас один код может работать, и на клиенте, и на сервере.

Инструкция по развертыванию примера:

  • Склонируйте репозиторий: git@github.com:mdenisov/isomorphic-react.git
  • Установите NPM зависимости: npm install
  • Запустите сборку приложения: gulp
  • Запустите: node server.js
  • Откройте в браузере: http://localhost:8000
  • Чтобы посмотреть рендеринг на стороне сервера, закомментируйте подключение файла main.js в /views/index.ejs.