⬆️ ⬇️

Develop isomorphic RealWorld applications with SSR and Progressive Enhancement. Part 1 - Introduction and Stack Selection

In the spring of 2017, Eric Simons , co-founder of the Thinkster training project, announced the RealWorld project - a demo application and specification for it. The project declared its goal to go beyond the usual “todo” -democ for a more practical comparison and study of the possibilities of various frameworks and technologies, as well as approaches to the development and ways of solving problems.



image





Introduction



About the project RealWorld



The author describes the idea and meaning of the project as follows:

So about a year ago, I had this problem:

')

Mastering the core concepts & ideology of a new framework is unnecessarily frustrating.



You’ll find out what you’re trying to do.



Except you're not. Not even close. If you’re really trying to get out of your own app, that's when you’ve wondered.



- Eric Simons



RealWorld is essentially a clone of a blog-social platform like Medium or Habr, called “Conduit” , which is developed by enthusiasts using different frontend and backend (yes, full stack) technologies according to the same specification and layouts. Anyone who wants to participate in the project needs to create a new issue in the repository on GitHub, in which to describe the desired technological stack, to fork the starter-kit project and start development. In addition, the developer has the opportunity to see the final result in action using a demo application written by Eric on AngularJS.



In fact, the result of each new implementation should be exactly the same application, but written using other technologies or approaches. Forks for React / Redux , Elm , Angular2 , React / MobX , Svelte / Sapper and other frameworks are already implemented and published. Vue , Ember and others are also on the way.







Among the backend technologies, the project is implemented on Node / Express, Laravel, Django, ASP.NET Core, Rails, and a whole lot more.







At the end of 2017, the project prepared a comparison of the 9 most popular frontend implementations on 3 criteria: performance (first meaningful paint), size of bundle (s) (gzip) and number of lines of code that are required for project implementation (loc).



At the moment, RealWorld is one of the best web development demo projects. It includes solutions to the most common tasks on the example of a fairly real web application and the current stack of technologies. The project has more than 12k stars on the githaba and, it seems to me, is quite useful and interesting. However, as is often the case, there is almost no information about him on Habré. Only a couple of mentions in digests .







What am i talking about



So, why am I writing all this? For the past few years, I have actually been an evangelist of an isomorphic (differently, “universal”) approach to writing web applications. Our company also practices the approach we call environment-agnostic apps , i.e. applications that can work in any environment (or rather, some kind of list of environments) without changes. This is especially important for us, because the company is developing a wide range of platforms from the web to various IoTs.



“Evangelist” is probably too loud a term, but during this time I participated in a considerable number of discussions and conferences, and also managed to read my own report on this difficult and controversial topic several times . In addition, I have long been very concerned about the problems of “accessibility” , “progressive enhancement” and “SEO” of modern web applications, also web-based applications.



Disclaimer about the isomorphic approach and disputes about it.
I am well aware of the fact that an isomorphic (universal) approach to development is a rather complicated and even holivar topic. Many articles have been written, many mines are broken. Quite a large number of developers do not accept this campaign, some act as outright haters.



Dear fellow developers, if for some reason you think that this approach to development does not have the right to life, I ask you to first see at least the introductory part of my report on YouTube, for example. Perhaps it will save me time for argumentation, and you will be saved from the desire to arrange a holivar on this topic in the comments to this or other articles of this tutorial.



Let's respect each other. I also ask you to respect the work that I plan to invest in the community’s benefit by developing this project and writing a detailed tutorial on the go. Please do not take this series of articles as forcing you or anyone else to use these approaches. World!



As part of my report, I implemented an isomorphic demo application in the form of a small blog based on one of the Material Design Lite templates . The application is, let's say, small, in fact, without a backend - its presence is only emulated using such things as JSON Placeholder .



To my shame, I myself learned about the RealWorld project only at the end of last year, but then I did not find enough time to participate in it. Now it has become a little easier with time and I want to try to implement the RealWorld application (instead of the current one) that meets the following characteristics:



"Manifest" of the project:



  1. Comply with the specifications of the project RealWorld;
  2. Fully support the work on the server (SSR and all the rest);
  3. On the client to work as a full-fledged SPA;
  4. Indexed by search engines;
  5. Work with JS disabled on the client;
  6. 100% isomorphic (common) code; *
  7. For implementation, do not use "half measures" and "crutches"; **
  8. Use the maximum simple and well-known technology stack; ***
  9. The size of the final bundle should not exceed 100Kb gzip;
  10. The number of lines of application code should not exceed 1000 loc.




* - about how to read the common code.
This is a bit of a controversial issue. I thought a lot about how exactly the general (isomorphic) code should be considered and came to the conclusion that such code is the code of the application itself written by me for its implementation. In other words, the code that implements the functionality of the current project, i.e. design-dependent code.



The part of the code that is only the basis of the application (for example, the web server code, etc.) and can be used without changes in another project will not be counted as an application code. You can call this code "starter-kit" or "boilerplate" or whatever. In any case, this code is not a direct part of a specific business problem and implements a generalized database, on the basis of which the application is written.



Once again, the application code will be considered as code written by me personally for the implementation of a specific project.



** - my understanding of crutches and half measures.
Studying examples of isomorphic applications, it can be noted that often not quite reasonable things are used, such as: (1) separate entry points for the server and client applications; (2) all sorts of conditional operators that check the current environment (ala "isServer", etc.) and separate branches of the code within the framework of common files; (3) server routing and data fetching is performed differently from the client, etc. etc.



It is this poorly grounded complication and obfuscation of the code, I suppose, that frightens not a small number of developers from trying. In my implementation, I will strive to use sound decisions of the architectural plan, instead of small crutches.



*** - about the stack.
Often, when it comes to isomorphic applications, people have a thought that it is necessary to use some special starter - whales , top - level - frameworks or other special things that change the usual stack of technologies.



Therefore, I will try to use the simplest, publicly available and well-known technology stack. In other words, reduce the use of any special tools to a minimum.



As you can see, the list is quite ambitious. Here you and controversial "isomorphism" and all the coveted, but such an unattainable "progressive enhancement". And a blog platform that works in the style of SPA, but at the same time supports SEO. All this on a single code base over the existing backend and according to the TOR, in which no changes can be made.



By the way about this. For me, this project is also interesting because many haters of the isomorphic approach often say that in order to write a full-fledged isomorphic application, you need to rewrite the backend in some special way. That is, your existing backend will not work. Or it is necessary to use NodeJS as a backend, which in itself is fu-fu-fu. Or that it is necessary in some special way to formulate requirements and TK and some other nonsense.



It is also quite common to find that isomorphic code is much more difficult and generally writing isomorphic applications is a bit difficult, it takes a lot of time and is not profitable economically. The last three points of my "manifesto", including, grow out of these errors.



In short, for me this project is also an opportunity to completely or partially refute all this, as well as have fun. I really hope that I will succeed. If you are interested in the topic and would like to go this way with me in steps - do not switch!



Stack selection



I'm sure you already understood, but if not, then I’ll clarify - we will write the frontend part of the application. For this, the RealWorld project provides the Frontend specification , which includes:





Full list of instructions can be found here .



So, at once I will try to execute p.8 of the manifest and choose the simplest stack as possible:





As you can see, while it looks pretty simple. There are no super-duper special solutions with the prefix "isomorphic-" and never will be.



Perhaps the “dark horse” on this list for many may be RactiveJS . Yes, indeed, the choice of a framework that is not part of the "big three" and does not hit record-breaking popularity may seem strange. However, it is excellent for this task, it is one of my favorite tools, and I hope a little to diversify the usual everyday life of “reactor-angulyar-view” tutorials. In addition, it is possible that by the same token I will also be able to present a new look at some familiar things. Although this is not my goal.



Server-side



Habr is still a technical resource and that this article in the end did not work out just a declaration of intent, perhaps already in this part we are implementing the initial code of the web server. This code does not depend on the specific implementation of the project. Well, as a matter of fact, I will simply take the code from the existing demo project, which I did for the report, and adapt it.



Disclaimer
This tutorial is intended primarily for frontend developers of middle and higher level. Who are familiar with modern development tools and know what SPA and isomorphism are.



As part of the tutorial, questions of installing npm-modules, introductory acquaintance with webpack, working with the command line or other basic things for today will NOT be revealed. I presume that for most readers routine operations for setting up the environment and working with development tools, etc. already familiar and debugged.



About architecture







Something like this will look like the overall architecture of the application. A few key points:



  1. The fontend part is divided into a client-server application on Ractive and on a purely server basis on Express / Node;
  2. RealWorld backend is located behind the frontend server;
  3. HTTP requests to the REST API are proxied through the frontend server on the backend.


Workflow



  1. The user enters the URL in the address bar;
  2. The client makes a synchronous request to the server;
  3. The server processes the incoming request and runs the main application code;
  4. At the moment when the application is ready, the server renders the current state of the application in HTML and returns a response;
  5. The client interprets the resulting HTML and begins to download related resources (CSS, JS, images, fonts, etc.);
  6. The user receives the content;
  7. The application code is downloaded in the background and initialized;
  8. The user interacts with the interface;
  9. Client application code processes actions, makes asynchronous requests to the server and updates the state.


The process is completely normal for any isomorphic web applications and will work just as well in this case.



Project structure



I will use a completely familiar structure of this project:











And of course, a whole bunch of various package.json , webpack.config.js and other configuration files, the description of which is beyond the scope of the tutorial.



Write the code



The main web server code will be located in the ./server.js file. In addition to Express itself, I will also use some of its plug-ins ( “express-middleware” ) to solve utilitarian tasks. Of course, all the necessary modules need to be pre-installed in package.json using the npm i command.



First, we connect the 3rd-party modules and Express itself:



const express = require('express'), helmet = require('helmet'), compress = require('compression'), cons = require('consolidate'); 


All Express extensions used are completely ordinary and are not related to isomorphism. Most of them are quite standard. Here is a brief description and purpose:





Next, create several middlewares that do nothing so far and connect them:



 const app = require('./middleware/app'), api = require('./middleware/api'), req = require('./middleware/req'), err = require('./middleware/err'); 






Let's connect the main server config common.json , initialize Express and extensions:



 const config = require('./config/common'); const server = express(); server.engine('html', cons.mustache); server.set('view engine', 'html'); server.use(helmet()); server.use(compress({ threshold: 0 })); server.use(express.static('dist')); 




Set mustache as a template engine for Express. Why him? Ractive simply uses mustache-syntax for its templates and in this way we will achieve uniformity.



Further we inform that we want to compress also the entire statit and that the static is located in the folder "./dist" ( bundles of webpack will be generated there too).



Next, the final touch:



 server.use(req()); server.all('/api/*', api()); server.use(app()); server.use(err()); server.listen(config.port); 


The code does not require special explanation. The key points here are as follows:





Summary web server code
 const express = require('express'), helmet = require('helmet'), compress = require('compression'), cons = require('consolidate'); const app = require('./middleware/app'), api = require('./middleware/api'), req = require('./middleware/req'), err = require('./middleware/err'); const config = require('./config/common'); const server = express(); server.engine('html', cons.mustache); server.set('view engine', 'html'); server.use(helmet()); server.use(compress({ threshold: 0 })); server.use(express.static('dist')); server.use(req()); server.all('/api/*', api()); server.use(app()); server.use(err()); server.listen(config.port); 




As it seems to me, even a purely frontend-developer, who does not have much experience with Express, will easily understand such a primitive code. Unless of course he understands what middleware is and this concept in general.



So far this code does almost nothing. In any case, does not do what is required to be done within the project. In the next article, we will add pure server code and proceed to the code of the application itself.



Thank you for your attention and good time of day!



UPD: Developing an isomorphic RealWorld application with SSR and Progressive Enhancement. Part 2 - Hello World



UPD 2: Updated the RactiveJS logo and added clauses 9 and 10 to the manifesto (see part 3 )

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



All Articles