⬆️ ⬇️

JS framework selection rules

TL; DR





The story began with a single mini-project that was originally developed based on the backbone.js and marionette.js libraries. These are, of course, the great libraries with which the history of the development of one-page applications began. But already at that time they were of historical rather than practical value. I will only mention the fact that in order to display a simple table, it was necessary to create: 1) a module with a description of the model, 2) a module with a description of the collection, 3) a module with a view definition of the model, 4) a module with a view definition of the collection, 4) a table row pattern, 5) table template; 6) controller module. Having about 10 entities in a small application - you already at the very beginning had more than fifty small modules. And this is just the beginning. But now is not about that.



At some point, after half a year of the application, it still did not appear in the search results. Adding prerender.io to the project (which then used the phantom.js engine) helped, but not as much as expected. And over the application, and clouds began to thicken above me, after which I realized that I needed to do something very quickly and efficiently, preferably today. The goal I set this: go to the server rendering. With backbone.js and marionettejs this is almost impossible to do. In any case, the rendr project on backbone.js, which was developed under the leadership of Spike Brehm (the author of the idea of ​​isomorphic / universal applications), gathered 58 contributors and 4184 likes on github.com, was stopped in 2015, and was clearly not intended for a one-day blitz . I started looking for an alternative. I excluded the TOP-3 JS framework immediately from consideration because I didn’t have enough time to master them. After a brief search, I found a rapidly developing JS framework riot.js github.com/riot/riot , which today has 13704 likes on github.com, and I hoped it could eventually come to the first position (which, however, did not occur).

')

And yes, I closed the question (though not on the same day, but for the next two days of the day) by transferring the application to server rendering using riot.js. And on the same day, when it happened, the site moved to the first ten pages of search results. And within a week I came out on the first page, from where it hadn't been leaving for several years.



At this success story ends and begins the story of defeat. The next project was much more complicated. The truth was a positive thing is that 99% of the application screens were in the user's personal account, so there was no need for server rendering. Inspired by the first successful experience of using riot.js, I began to promote the idea of ​​consolidating success and applying riot.js on the front end. Then it seemed to me that, finally, a solution was found which combined simplicity and functionality, as promised by the riot.js documentation. How wrong I was!



I ran into the first problem when it was necessary to provide the HTML layout maker with all the necessary tools for development. In particular, plug-ins were needed for the code editor, an engine in which it would be possible to place components and immediately observe the result obtained, including with hot component reloading (hot-reload). All this had to be given in the form ready for commercial use in the near future, and all this was not. As a result, the layout of the application started on one of the traditional templating engines, which as a result led to the ungrateful stage of translating HTML documents into riot.js components.



However, the main problem that emerged at this stage is not even connected with the translation of the layout from the template format into the riot.js components. Unexpectedly, it turned out that riot.js produces completely non-informative template compilation error messages, as well as run-time errors (this is true for all versions of riot.js up to version 4.0, which has been completely redesigned). There was no information not only about the line in which the error occurred, but even about the name of the file or component in which the error occurred. It was possible to search for an error for many hours in a row, and still it would not be found. And then it was necessary to roll back all changes to the last working state.



The next one was a problem with routing. Routing in riot.js goes almost out of the box github.com/riot/route - in any case from the same developer. It allowed to hope for its trouble-free work. But at some point I noticed that some pages are unpredictably overloaded. That is, once a transition to a new route can occur in a single-page application mode, and another time the same transition overloaded the entire HTML document, as when working with a classic web application. At the same time, naturally, the internal state was lost, if it was not yet stored on the server. (In the present development of this library is stopped and with the version riot.js 4.0 it is not used).



The only component of the system that worked, as expected, was the minimalist flux-like state manager github.com/jimsparkman/RiotControl . However, to work with this component, it was necessary to assign and cancel listeners of state changes much more often than we would like.



The original intent of this article was to show the tasks that the riot.js framework has to do with the tasks that the developer, who decided (decided) to develop the application on the JS framework not from the TOP-3 list, will have to solve. However, in the process of preparation, I decided to refresh some pages from riot.js documentation, and so I learned that a new version of riot.js 4.0 was released, which was completely (from scratch) revised, which can be found in the article by the riot developer. js at medium.com: medium.com/@gianluca.guarini/every-revolution-begins-with-a-riot-js-first-6c6a4b090ee . From this article I learned that all the main problems that worried me, and which I was going to talk about in this article, were eliminated. In particular, in riot.js version 4.0:





This is not an advertisement, just a background. In fact, my conclusions will be quite ambiguous in terms of recommendations for use, and the article is generally devoted not to a specific framework or library, but to tasks that need to be solved in the development process.



Unfortunately, the work done by the developers of riot.js has not yet been properly appreciated by the community. For example, the server rendering library github.com/riot/ssr for the six months past from the beginning of its development gathered three distributors and three likes on github.com (not all likes are made by contributors, although one is a contributor).



Therefore, in the course of the play I changed the direction of the article, and instead of memoirs I tried again to go all the way, having a little more knowledge and experience, more advanced versions of libraries and unlimited free time.



So here we go. For example, the implementation of the application github.com/gothinkster/realworld was made. This project has been discussed more than once in Habré. For those who are not familiar with him, I will briefly describe his idea. The developers of this project with the help of different programming languages ​​and frameworks (or without them) solve the same problem: developing a blog engine, with functionality similar to the simplified version of medium.com. This is a trade-off between the complexity of real-world applications that we have to develop every day, and todo.app, which does not always allow us to really appreciate the work with the library or framework. This project is respected by developers. In confirmation of this, I can say that there is even one implementation from Rich Harris (major developer sveltejs) github.com/sveltejs/realworld .



Development environment



Of course, you are ready to rush into battle, but think about the developers around you. In this case, focus on the question of the development environment in which your colleagues work. If for the framework with which you are going to work, there are no plug-ins for the main development environments and program code editors, then you are unlikely to be supported. For example, I use Atom editor for development. For it there is a riot-tag plugin github.com/riot/syntax-highlight/tree/legacy , which has not been updated for the last three years. And in the same repository there is a plugin for sublime github.com/riot/syntax-highlight - it is current and supports the current version of riot.js 4.0.



However, the riot.js component is a valid fragment of an HTML document in which the JS code is contained in the body of the script element. So everything just works if you add the html document type for the * .riot extension. Of course, this is a forced decision, since otherwise it would have been impossible to continue further here.



We got the syntax highlighting in a text editor, and now we need more advanced functionality, what we used to get from eslint. In our case, the JS code of the components is contained in the body of the script element, I was hoping to find and found a plugin for extracting the JS code from the HTML document - github.com/BenoitZugmeyer/eslint-plugin-html . After that, my eslint configuration began to look like this:



{ "parser": "babel-eslint", "plugins": [ "html" ], "settings": { "html/html-extensions": [".html", ".riot"] }, "env": { "browser": true, "node": true, "es6": true }, "extends": "standard", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" }, "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" }, "rules": { } } 


The presence of plug-ins for syntax highlighting and eslint is probably not the first thing that a developer begins to think about when choosing a JS framework. Meanwhile, without these tools, you may encounter opposition from colleagues and their exodus for "valid" reasons from the project. Although the only and truly valid reason is that they are not comfortable working without having a full developer’s arsenal. In the case of riot.js, the problem was solved by the Columbus method. In the sense that there are actually no plug-ins for riot.js, but thanks to the syntax of the riot.js templates, which looks like a fragment of a regular HTML document, we cover 99% of the necessary functionality using tools for working with an HTML document.



In addition to the tools for writing and validating the code that we have just considered, the developer’s tools include tools for quickly assembling and debugging a project, hot rebooting components and modules in a web browser when making changes to a project. This part will be covered in the next section.



Build project



We have already managed to get used to most of the features that are needed when building a project, and even stop thinking about what could be different. But otherwise maybe. And, if you have chosen a new JS framework, it is advisable to first make sure that everything works as you expect. For example, as I mentioned, the greatest problems in developing on older versions of riot.js were related to the lack of compilation error messages and the run-time information about the component in which this error occurred. Also important is the compilation speed. As a rule, to speed up the compilation speed, in correctly built frameworks only the changed part is recompiled, as a result, the response time to changes in the text of the components is minimal. Well, quite well, if supported by hot reloading components without a full reload of the page in a web browser.



Therefore, I will try to list the checklist, on which you need to pay special attention when analyzing the means of assembling a project:



1. Availability of development mode and working application

In developer mode:

2. Informative project compilation error messages (source file name, line number in source file, error description)

3. Informative runtime error messages (source file name, line number in source file, error description)

4. Quick rebuild of modified modules

5. Hot reloading of components in the browser.

In operation:

6. The presence of versioned in the file name (for example 4a8ee185040ac59496a2.main.js)

7. The layout of small modules in one or more modules (chunks)

8. Breaking the code into chunks using dynamic import.



In riot.js version 4.0, the github.com/riot/webpack-loader module has appeared, which fully complies with the given checklist. I will not list all the features of the assembly configuration. The only thing I’ll notice is that in this project I use modules for express.js: webpack-dev-middleware and webpack-hot-middleware, which allow you to work on a full-featured server right after the layout. This, in particular, allows the development of universal / isomorphic web applications. Please note that the hot component overload module is valid only for a web browser. At the same time, the component rendered on the server side remains unchanged. Therefore, it is necessary to listen to its changes, and at the right time, delete all the code cached by the server and load the code of the modified modules. How to do this for a long time to describe, so just give a link to the implementation: github.com/apapacy/realworld-riotjs-effector-universal-hot/blob/master/src/dev_server.js



Routing



Slightly paraphrasing Leo Tolstoy, we can say that all the engines of the JS frameworks are alike, while all the routings that are attached to them work in their own way. I often see conditional classification of routers into two types: declarative and imperative. Now I will try to figure out how such a classification is justified.



We will conduct a brief excursion into history. In the early days of the Internet, the URL / URI corresponded to the name of the file that is hosted on the server. Turn over several pages of history and we will learn about the advent of Model 2 (MVC) Architecture. In this architecture, a front controller appears that performs the function of routing. I wondered who first decided from the front controller to allocate the routing function into a separate block, which further sends a request to one of many controllers and has not yet found the answer. It seems that this started to do everything at once.



That is, the routing determined the action to be carried out on the server and (transitively through the controller) the view that will be generated by the controller. When transferring the routing to the client side (web browser), an idea of ​​the routing function in the application has already been formed. And those who primarily focused on the fact that routing determines action, developed imperative routing, and those who paid attention to the fact that, finally, routing determines the view that should be shown to the user, developed declarative routing.



That is, when transferring from server to client to routing, “hung” two functions that were characteristic of server routing (choice of action and choice of twist). In addition, new tasks have arisen - this is navigating through a one-page application without completely reloading the HTML document, working with the history of visits and much more. For illustration, I will quote excerpts from the documentation of a router of one mega-popular framework:



... makes it easy to create SPA-applications. Includes the following features.





In this version, routing is clearly overloaded with functionality and needs to be rethought on the client side. I began to look for a solution suitable for my problem. As the main criteria, I took into account that the routing:



  1. should work equally on the web client side and on the web server side for universal / isomorphic web applications;
  2. should work with any (including chosen by me) framework or without it.


And I found such a library, it is github.com/kriasoft/universal-router . If you briefly describe the idea of ​​this library, it configures the routes, which accept the URL string as input, and call an asynchronous function at the output, which is passed to the parsed URL as an actual parameter. To be honest, I wanted to ask: is that all? And how to continue with this all need to work? And then I found an article on medium.com medium.com/@ippei.tanaka/universal-router-history-react-97ec79464573 , which offered a fairly good option, except perhaps rewriting the push () history method, which didn’t needed and which I deleted from my code. As a result, client-side router operation is defined like this:



 const routes = new UniversalRouter([ { path: '/sign-in', action: () => ({ page: 'login', data: { action: 'sign-in' } }) }, { path: '/sign-up', action: () => ({ page: 'login', data: { action: 'sign-up' } }) }, { path: '/', action: (req) => ({ page: 'home', data: { req, action: 'home' } }) }, { path: '/page/:page', action: (req) => ({ page: 'home', data: { req, action: 'home' } }) }, { path: '/feed', action: (req) => ({ page: 'home', data: { req, action: 'feed' } }) }, { path: '/feed/page/:page', action: (req) => ({ page: 'home', data: { req, action: 'feed' } }) }, ... { path: '(.*)', action: () => ({ page: 'notFound', data: { action: 'not-found' } }) } ]) const root = getRootComponent() const history = createBrowserHistory() const render = async (location) => { const route = await router.resolve(location) const component = await import(`./riot/pages/${route.page}.riot`) riot.register(route.page, component.default || component) root.update(route, root) } history.listen(render) 


Now, any call to history.push () will trigger routing. To navigate inside the application, you also need to create a component that wraps the standard HTML element a (anchor), not forgetting to cancel its default behavior:



 <navigation-link href={ props.href } onclick={ action }> <slot/> <script> import history from '../history' export default { action (e) { e.preventDefault() history.push(this.props.href) if (this.props.onclick) { this.props.onclick.call(this, e) } e.stopPropagation() } } </script> </navigation-link> 


Application State Management



Initially, I included the mobx library in the project. Everything worked as expected. With the exception of the fact that it did not quite fit the task - the research that I set at the beginning of the article So I switched to github.com/zerobias/effector . This is a very powerful project. It gives 100% of redux functionality (only without large overheads) and 100% mobx functionality (although in this case it will be necessary to encode a bit more, but still less if compared with mobx without decorators)



The description of the store looks like this:



 import { createStore, createEvent } from 'effector' import { request } from '../agent' import { parseError } from '../utils' export default class ProfileStore { get store () { return this.profileStore.getState() } constructor () { this.success = createEvent() this.error = createEvent() this.updateError = createEvent() this.init = createEvent() this.profileStore = createStore(null) .on(this.init, (state, store) => ({ ...store })) .on(this.success, (state, data) => ({ data })) .on(this.error, (state, error) => ({ error })) .on(this.updateError, (state, error) => ({ ...state, error })) } getProfile ({ req, author }) { return request(req, { method: 'get', url: `/profiles/${decodeURIComponent(author)}` }).then( response => this.success(response.data.profile), error => this.error(parseError(error)) ) } follow ({ author, method }) { return request(undefined, { method, url: `/profiles/${author}/follow` }).then( response => this.success(response.data.profile), error => this.error(parseError(error)) ) } } 


This library uses full-fledged reducers (they are called so in effector.js documentation), which many lack in mobx, but with strikingly less coding efforts, compared to redux. But the main thing is not even that. Having received 100% of the functionality of redux and mobx, I used only a tenth of the functionality that is incorporated in effector.js. From which we can conclude that its use in complex projects can significantly enrich the means of developers.



Testing



Todo



findings



So, the work is complete. The result is presented in the github.com/apapacy/realworld-riotjs-effector-universal-hot repository and in this article on Habré.

Demo site on now realworld-riot-effector-universal-hot-pnujtmugam.now.sh



And at the end I will share my impressions of development. Developing on riot.js version 4.0 is quite convenient. Many constructions are written easier than in the same React. The development took exactly two weeks without fanaticism in the after-work and on weekends. But ... One small but ... The miracle did not happen again. Server rendering in React is 20-30 times faster. Corporations are winning again. However, two interesting routing libraries and a state manager have been tested.



apapacy@gmail.com

June 17, 2019

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



All Articles