📜 ⬆️ ⬇️

Building Reliable Web Applications with React: Part 4, Server Generation

Translation of the article “Building robust web apps with React: Part 4, server-side rendering”, Matt Hinchliffe

From the translator: this is the fourth part of the series of articles “Building robust web apps with React”
Translations:

A few months ago I wrote the first part of this series , I was very interested in the possibilities of using React to create intelligent applications that can avoid the weaknesses that exist in many modern JavaScript applications. Finally, I'm going to make my application run first on the server, and then run the same code in the browser, completing the loop of an isomorphic or adaptive hybrid application.

In the previous part of this series, I wandered around in search of a testing strategy, and this part is not much different. And although there are examples and basic demo of isomorphic JavaScript, there are not so many open source implementations to learn. Towards the implementation details, for me, the biggest difference from writing client-side JavaScript is that I initially have the data.

Reorganization of data flow


The browser version of my Tube Tracker application has a very simple data flow model, but this model is not entirely suitable for the server side. In the browser, data is requested by an AJAX request as soon as the application loads, but an isomorphic application must provide ready-made HTML pages to the browser.
')
The React application has one access point to the component stack — the root component is drawn by the renderComponent or renderComponentToString , so the data must flow to the top of the stack and be sent down. This goes against ordinary thinking when working with React, since the data should be processed only by the component that needs it.

image

Since the data comes down the stack, components that were originally supposed to request data can now use the getInitialState method as preload data. Otherwise, the components do not change, except that the initial state loading stage is skipped.

 // ~/app/component/predictions.jsx var Predictions = React.createClass({ getInitialState: function() { return { status: this.props.initialData ? "success" : "welcome", predictionData: this.props.initialData }; }, ... }); 

The data provided to the root of the application on the server side must also be obtained on the client side , if the application must be loaded without the same data provided on the client side, then it will be re-branded to its initial, empty state. The easiest way to provide the initial data is to generate them into the script element, and then pick them up when the application is initialized:

 // ~/app/browser/bootstrap.jsx var React = require("react"); var networkData = require("../common/data"); var TubeTracker = require("../component/tube-tracker.jsx"); window.app = (function() { ... var initialData = JSON.parse(document.getElementById("initial-data").innerHTML); return React.renderComponent(<TubeTracker networkData={networkData} initialData={initialData} />, document.body); })(); 

React on server


Components that use plain JSX syntax must be transformed into plain JavaScript before use, but on the server this does not necessarily mean a precompile step. The React Tools library can transform on the fly. And it is packaged in a Node-JSX package that transparently interprets the modules as they are needed. Node-JSX needs only a one-time connection to the application, since the require method works globally, but use this carefully: the parser used by the React Tools library can rest on some particularly creative aspects of using JavaScript, so for security and performance reasons, it’s better to pull components in files with the extension .jsx .

 // ~/server.jsx require("node-jsx").install({ extension: ".jsx" }); var config = require("./config"); var express = require("express"); var API = require("./app/server/api"); var Bootstrap = require("./app/server/bootstrap.jsx"); 

The component stack is generated in the same way as in the browser, but instead of creating a dynamic tree, only the HTML string is needed. For these purposes, React provides the renderComponentToString method, it simply runs the getInitialState and componentWillMount methods for each component. Components should be translated to the server without causing problems, unless browser-browser code is used anywhere, so make sure that all client code is moved to the componentDidMount method.

The rest is on the server


The last few steps to provide the initial HTML code to the browser are in a specific implementation, but for reference, I will quickly go through the backstage implementation of the Tube Tracker application.

The application has already used Express to provide static assets, I added an additional route to process requests and respond to them with static HTML. This route, if needed, makes a request to the API and drives the data into the template loaded from the file system:

 // ~/server.js app.get("/", function(req, res) { new API(config).for(req.query.line, req.query.station).get(function(err, data) { if (err) { return res.send(500, "API error"); } new Bootstrap(data).load(function(err, responseHTML) { if (err) { return res.send(500, "Template error"); } res.send(responseHTML); }); }); }); 

The template module is extremely simple; it loads the requested file from the disk and replaces the placeholders with the transferred data. It makes no sense to use a more complex library for templating, since all this template engine will work with are two small pieces of data:

 // ~/app/server/template.js var fs = require("fs"); var path = require("path"); function Template(target) { this.target = target; } Template.prototype.render = function(data, callback) { var fullPath = path.resolve(__dirname, this.target); fs.readFile(fullPath, { encoding: "utf8" }, function(err, template) { if (err) { return callback(err); } var rendered = template.replace(/\{\{yield:([a-z0-9_]+)\}\}/g, function(match, property) { return data[property]; }); callback(null, rendered); }); }; module.exports = Template; 

Due to browser incompatibility, it is no longer possible to create a complete HTML document using React components. It would be possible not to do this on the server, but it would be strange to create components that are differently arranged on the backend and the frontend.

I took the opportunity to replace the old and unfriendly TrackerNet API with the new TfL REST API. This replacement slightly reduces the complexity of the work, since it avoids working with XML, but the more convenient JSON data for use from the new API is returned unsorted and lacking some useful information. For this reason, I built a small proxy to sort and add additional useful data. In the future, you can add an interlayer to cache and prevent unnecessary calls to the API.

Conclusion


Building applications that should run on the server and in the browser was an interesting adventure. React is a great tool for building client applications and it gives you great productivity because with it, it’s really easy to build dynamic applications. With careful planning, he will help turn the fragile client-side JavaScript application into a reliable, working product. However, for now, I do not strongly recommend isomorphic JavaScript applications; The increased complexity of development has turned the construction of the Tube Tracker application into hard work. The browser version of the application took several hours of development, and the server version even less, but structuring and abstracting the code to run the applications as a whole took many times longer than expected.

image
A relatively small amount of project code is divided between the two environments, only well-abstract utilities and React components have been transferred.

Undoubtedly, the time spent on building isomorphic applications will decrease as soon as this process becomes more frequent and new templates appear, but now I’m worried that the extra effort spent is just a developer’s whim. If the application generated on the server is sufficiently functional, then there is no really convincing argument that the user experience will be improved by loading and executing a large amount of JavaScript that only repeats the application that the user already has.

The efforts of web developers to repeat the behavior of applications for smartphones led to a frantic revolution in the technologies we used and we learned a lot in using the web as a platform for delivering all kinds of content. Some of the features and techniques that have emerged that are isomorphic to JavaScript are very interesting, but building new web products continues to be increasingly difficult.

I really like React, it's a great tool, but I like it when it's simple.

You can try the finished application right now (note: the example is running on a free account, so this link may be unstable) or go to GitHub to view the source code . Please comment or tweet me , I will be happy to receive feedback.

Source: https://habr.com/ru/post/230269/


All Articles