Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / React

Component Based Web Application

4.94/5 (9 votes)
13 Nov 2015CDDL21 min read 27.5K  
Web application evolves to be more component-based, data-driven and service oriented. This article documents what we’re looking for and what we’ve found when evaluating Polymer and React for component-based web application development.

Introduction

About three months ago, I got a question to answer: we need to scale up our web application, also need to evolve the architecture to be more modular and componentized as we packing in more features, which technology should we use?

Additionally, our web app has already been in production serving customers, no matter which technology we decide to go with, it can not break current functionality, existing release cycle needs to continue and maintain, the migration needs to be totally transparent to end users. 

This really means the new componentizing effort needs to be a “phased transition”, we need to "slice in" new component to replace Angular Directives gradually. It requires the new component technology to work with current Angular app shell smoothly, and eventually moving away from Angular to the new architecture.

As part of technology evaluation process, here are the sub-questions I need to look into:

  • What are the options worth deep diving?
  • What's the component model? How is it encapsulated and sandboxed?
  • What's the intra-and-inter-component interaction mechanism?
  • How data integration / data flow works? Two-way binding or unidirectional?
  • How is the loading/rendering performance?
  • What's the programming model? Imperative or Functional?
  • Is it production ready and future-proof? Does it has broad community support?
  • Finally, which technology we should start to migrate to?

Before we answer these questions, let's take one step back to look at the big picture first.

Background

As web application grows and scales, "decoupling" is the theme of technology evolution. Server side monolithic application had been decoupled to be n-tier client-server architecture long time ago, while server pages technologies were able to be further decoupled as native client app and sharable APIs a few years ago, like Service Oriented HTML Application (Ajax/Multi-page/Session Sync). Then Single Page Application simplified and improved it with client side JavaScript frameworks and server side RESTful APIs currently. Web applications have been constantly evolving to be more and more component-based, data-driven and service oriented these days.

Component based web application continues the trend to make SPA / SOHA even more composable and performant. It promotes encapsulation and compartmenting with reusability, composability and modularity, keeps critical business logics in server side data service API, starts to decouple application features further more to bring reusability, maintainability and scalability to the next level for large scale web applications.

One beauty of this evolution is that server side APIs are increasingly to be more application agnostic, totally reusable by different types of native clients (web, native mobile, desktop app, etc.) and server side consumers. No change on the service / APIs side is required while the client side is moving towards componentized. Although isomorphic JavaScript needs some server side changes, but I'm not convinced it's the future of web app at this point even for solving initial performance issue, instead, I strongly believe in component based web app is the future.

Almost all prominent JavaScript frameworks recognize the importance of “component”, each of them has the concept of "component" one way or another, including Ember, Angular, ExtJS and Backbone, and of cause JQuery UI and Plugin. But sadly, each component model is different, not compatible with each other, even design principles and runtime behavior are also different. In the other hand, current web platform's evolution trend renders the need of monolithic framework to be out of date.

Some well-respected industry influencers are also proponents for moving away from monolithic JavaScript frameworks. Jimmy Breck-McKye penned an article, The State of JavaScript in 2015, proposed to “Prefer dedicated libraries to monolithic frameworks”. Cody Lindley, author of JavaScript Enlightenment and DOM Enlightenment, also tweeted "notion that a monolithic JS app framework solution is best for enterprise is fallacious. All in 1 JS frameworks are the problem not solution." And, Joe Gregorio’s well-read article, No more JS frameworks, contributed to the creation of the phrase ‘zero framework manifesto’.

Web platform is constantly evolving and component based web application is the future. With today's technologies, we can evolve our web application, moving it from highly opinioned monolithic framework to be more modular and componentized. Self-contained reusable composable components also improve productivities, and ultimately, it enables effective feature creations and rapid iterations to deliver values to customers more efficiently.

Our first question is: what are the options currently?

Options

Currently, Angular Directives is our component model in production. Technically, all frameworks mentioned above are potential candidates, each one of them has a way to create “components”. Since we decided to move away from monolithic frameworks, the following options are out of picture:

  • AugularJS 2.0 (alpha): although redesigned from ground up, it's lack of backward compatibility, written in TypeScript instead of ES6 / ES2015, lacking of clear strategy to collaborate with Polymer (if "there is an element for that", why do I need Angular 2.0?), are all red flags;
  • EmberJS: although it's an one-stop-shop for different aspects of web development, we've already got our own infrastructure and tools on development, I’m not a big fan of Ember's strong opinions;
  • Backbone: light-weight while powerful, but views are not decoupled and has too many dependencies on other Backbone specific constructs. Ampersand.js inherits backbone's powerfulness while "decouples everything" by breaking into smaller plug-able npm modules, it's a very valuable and interesting efforts. But it still lays in imperative data binding model, and the npm modules quickly bloated into "too many to manage" messy packages in my fairly simple experiment app.
  • Vue.js: its simplicity and ease of use gains popularities recently, similar to React.js, it’s just a view layer, also highly inspired by Angular.js and Polymer. Based on the documentation on comparison with other frameworks, Vue.js really tries to address the problems and issues in Angular.js, while keeping HTML templates, imperative DOM manipulation and data-binding mechanism in, compared to React.js. Although the community supports are expanding these days, Vue.js just became “production ready” a couple of months ago, it’s changing and evolving fast with lots of releases. Also because of this newborn nature, it’s also lack of broad and established “add-on” libraries, like routers and data flow integrations, etc. At this stage, it’s not suitable to treat it as a candidate to serve our production web app, but it’s worth staying in our watch list.

Additionally, Mithril JS has a surprisingly fast virtual DOM implementation with a very small footprint, simple and clear APIs for "component", it’s a very notable alternative for light-weight fast web application library. Just as Vue.js trying to solve the shorting comings from Angular.js, Mithril JS really tries to solve the heaviness of React.js. Due to it is still in infancy stage and not having broad enough community support, we decided to put it to the watch list too.

Now it really leaves us with two options: Web Components with Polymer and React with Flux.

Overview

Both Polymer and React supports composable components with declarative markups, both has strong expressiveness with simple APIs, both can work with our AngularJS app shell in transition time, and both have heavy influences on web development. 

React prompts us to "rethink best practices", even "redefines" best practices: instead of treating HTML as first class citizen of web, it generates HTML from JavaScript; Instead of building class based external CSS, it promotes in-line styles and creating style through JSON objects; Instead of data binding, React encourages application to render the "whole page" when state/property changes; Instead of a MVC, it introduces the notion of "controller view" that handles top level component state and passes it down to children components as properties, etc. When those long held "best practices" all collapsed in the world of React, it does demand some time to understand it and get hands dirty to actually see its benefits.

Apparently, "Best practices" is an overloaded and laughable term, it's there to reference current “good” practices, sometimes needs to be thrown away completely for “better”.

While we are keeping an open mind to React, Polymer looks very promising too. In addition to open source and build practical custom elements around it, Web Components are already part of W3C standard, gaining browser native supports beyond Chrome, also winning community support and ecosystem is expanding as well. All these are key considerations before make long-term commitment. I'm totally on board with web components philosophy: DOM element is the component model on web, evolve it.

Specifically, web components use Custom Element to enable developers expanding HTML vocabulary for both visual and non-visual functionalities, also use optional HTML template and Shadow DOM for implementation, then packaged / imported by HTML Imports. Very intuitive and native to web.

Simplicity and performance are compelling features from React, while intuitive-ness and standardization are the highlights of web components. Because we are making technology choices for production, we need to do deep dives before making a sound recommendation.

Let's take a look at the component model first.

Encapsulation

Encapsulation in component model consists of three aspects that is important for our production:

  1. Runtime isolation: clear boundary between itself and its container, to protect or hide component details (styles, scripts, etc.) and children elements
  2. Delivery packaging:  flexible to package, supports both bundled with hosting app or lazy-loading as a self-contained downloadable package
  3. Development structure: independent source code organizations for components JavaScript, HTML and CSS regardless how component is packaged or delivered

Runtime isolation

Web Components' shadow DOM provides the sandbox for custom element, hosting document's script will not able to access the inside of shadow DOM via DOM traverse API, like querySelector, getElementById, etc. Additionally, no "CSS style leak" from hosting document to components, and vise versa. Although JavaScript is not sandboxed in current spec, this DOM level runtime encapsulation provides very clear boundary and protection between hosting document and custom element.

However, only Chrome and Opera has native support at the time of this writing, Firefox supports it with opt-in flag, while other browsers need to have polyfill to make it work. The reality is that it's technically difficult to polyfill Shadow DOM with JavaScript, so polymer 1.0 switch its default to Shady DOM. Shady DOM offers consistency and performance cross browsers, but lost the encapsulation of DOM tree and styles.

Obviously, Polymer 1.0 does not realize the promises of encapsulation of Shadow DOM out of box. From style protection and local DOM sandboxing perspective, Polymer's Shady DOM implementation is almost the same as React's Virtual DOM, while React's Virtual DOM is never designed for encapsulation. 

At current stage, there is no winner in terms of runtime encapsulation, the promise of Virtual DOM encapsulation will rely more on browser's native support than polyfills.

Packaging

Polymer is the clear winner in terms of component packaging via HTML Import. It's up to the application to bundle it via Polymer's build tool Vulcanize, or host / import a self-contained component (HTML, CSS and JavaScript files) at runtime when lazy-loading is desired.

In contrast, React component are not self-contained (unless everything is inline) and lazy-loading is not as intuitive as Custom Elements.

Here is an example to load a self-contained web component via HTML import:

HTML
<link rel="import" href="scripts/dev-studio/lib/studio-navbar/studio-navbar.html" />

Then in HTML body to instantiate it:

HTML
<studio-navbar project-info="{{projectInfo}}" ng-controller="NavBar" action-items="{{actionItems}}"></studio-navbar>

*note: the ng-controller is the hook back to exiting Angular app shell, it’ll go away once we completely move off Angular

However, the reality is that there are some caveats to actually utilize HTML Import, especially when incorporating multiple web components from different teams or providers. For example, if component A depends on jQuery 1.9, and component B has dependency on jQuery 2.1, the browser would load both. Given JavaScript is not sandboxed, it can cause undesired behaviors. Before JavaScript sandboxing is implemented, all component providers need to agree upon and stick to a common dependency management policy, but sometimes it's so easy to get out of sync, even within the same company. 

Code Structure

Both polymer and react are not dedicating how source code should be structured, polymer supports both external JS/CSS and inline, although I found I have to inline JavaScript code in order to make it work in Firefox before V1.1. Nowadays, with Browserify and Babel are deployed to almost all of new web application projects, developers have full control over how component source codes should be structured.

Now that Polymer uses Custom Elements as component model while React components are all derived from React.Component base class (with or without JSX), we can move on to look at differences on how components interactions work.

Composition

Composition is also essential to component model, here is what we’re wishing for:

  1. Components can be easily embedded / nested with clear interfaces, default behavior / state should be easily obtained and customized
  2. Component package (implements specific feature) should be autonomous, pluggable and style-able
  3. No direct inter-component (sibling) interaction / communication is allowed, intra-component interaction mechanism should be cleared defined (parent-children)

Polymer's composition feature works in the same way as regular HTML elements, it's more intuitive than React's JSX syntax. Syntactically, they are very similar, although Polymer utilizes standard HTML while React employs JSX. JSX is just a syntax sugar, it’s always compiled to JavaScript functions. From component consumer perspective, both are highly composable and customizable.

Here is an example of composed React component:

HTML
'use strict';

import React from 'react';

import NavBarMenus from './react.navbar.menus';
import NavBarLinks from './react.navbar.links';

class StudioNavBar extends React.Component { 
   static get defaultProps() { 
      return {}; 
   } 
 
   constructor(props) { 
       super(props); 
   }
    
   render() { 
      return ( 
         <div> 
             <NavBarMenus></NavBarMenus> 
             <NavBarLinks></NavBarLinks> 
         </div> 
      ); 
   } 
} 
 
module.exports = StudioNavBar; 
<navbarmenus> <navbarlinks>

 

Obviously, inter-component interaction is contradicting the concept of "component" or “encapsulation”, both polymer and react are not allowing it, sibling components are never talking to each other directly, they work together through their parent.

Polymer call the parent component that facilitates child-parent commutations the "mediator pattern", while React name it as "Controller Component" or "Container". They all work in the same way: child components needs to notify parent when something occurs, it’s up to the parent component to handle it, either notify other child component or bubbling up.

Again, Polymer has intuitive and standard event mechanism, custom events works in the same way as built-in HTML element event, including event bubbling. In my test app, a polymer web component is buried fairly deep as a composition test, its custom event works seamlessly with document.onclick event handler that expects event bubbling.

By comparison, React component has no concept of event bubbling. The recommended way is to use Flux unidirectional data flow: child component raises Action with data, while Action dispatches to listening Store, Store incorporate data changes (or the Action’s Ajax result of CRUD), then emit change event. The "Controller Component" or "Container" responses to the Store event via callbacks, updates component “state”, then propagate state changes as child’s components property for redraw.

Here is example code of the same component shown above but with unidirectional data flow:

HTML
'use strict';

import React from 'react';
import navbarStore from './react.navbar.store';
import navBarActions from './react.navbar.actions';

import NavBarMenus from './react.navbar.menus';
import NavBarLinks from './react.navbar.links';

class StudioNavBar extends React.Component { 
   static get defaultProps() { 
      return {}; 
   } 
 
   constructor(props) { 
       super(props); 
       this.state = navbarStore.get(); 
 
       this._onChange = this._onChange.bind(this); 
       this.onNavBarAction = this.onNavBarAction.bind(this); 
   } 
 
   componentDidMount() { 
      navbarStore.addChangeListener(this._onChange); 
   } 
 
   componentWillUnmount() { 
      navbarStore.removeChangeListener(this._onChange);   
   } 
 
   _onChange() { 
      this.setState(navbarStore.get()); 
   } 
 
   render() { 
      return ( 
         <div> 
             <NavBarMenus projectInfo={this.state.projectInfo} menus={this.state.menus}></NavBarMenus> 
             <NavBarLinks links={this.state.links} ></NavBarLinks> 
         </div> 
      ); 
   } 
} 
 
module.exports = StudioNavBar;

 

At first glance, Flux’s unidirectional data flow sounds convoluted, which leads me to look deeper at data integrations differences between the two.

Integration

Our web application is heavily data driven, a component model’s data integration mechanism we’re looking for are more on the efficiency and performance considerations:

  1. Supports Service Oriented Architecture, requires no change on existing client / server integration via Ajax and RESTful APIs
  2. Enables state-less server side API implementation by keeping user sessions in the client
  3. Incremental data changes, (results of user interaction or API responses) should be handled efficiently, views reflects and sync with data/model all the time

Both Polymer and React is not full-blown framework, both doesn’t have strong opinion on how session is maintained and how application state/session data is retrieved. This is exactly what we need, we’ve already got fair amount Ajax code already for client / server integrations, our RESTful APIs are fairly stable and also used by mobile apps, we don’t want to change that while evolving front end.

Architecturally, both item #1 and #2 are well supported / (or no restrictions) by Polymer and React, the real difference really replies on #3.

Polymer 1.0 has constrained two-way data binding in exchange for performance, sometimes I was forced to use cumbersome “computed property”, missing the cleanness and convenience of mustache-like data binding syntax. When it comes to heavy collection bindings, you may also need to directly manipulate the DOM to achieve performance.

In terms of data integration, Polymer is still in the traditional data-binding world, component-in and component-out data are through classic HTML element’s attributes/properties and events. In comparison, React is more mind-blowing, because React essentially tells us no need for two-way data binding, and unidirectional data flow is preferred: data only flows from "Controller Component" or "Container" down to child component, so that consistent, simplified and deterministic state management becomes possible.

Actually, React doesn’t require unidirectional data flow, it enables one-way data flow instead. 

For large-scale data driven applications, React Virtual DOM design principles and runtime performance really shines, it not only greatly simplifies state/properties/data management for components, but also make higher-level application state management a breeze. No need to deal with the potentially cascading data/view circular updates resulting from two-way data binding, React really enables us to easily identify the single source of truth for deterministic application/component state.

Here below is the same composite component that utilizes Reactive Extensions for unidirectional data flow (rather than Flux):

HTML
'use strict';

import React from 'react';

import NavBarMenus from './react.navbar.menus';
import NavBarLinks from './react.navbar.links';

import navBarAction from './reactive.navbar.action';
import navBarStore from './reactive.navbar.store';

let StudioNavBar = React.createClass({
    getDefaultProps() {
        return {};
    },

    getInitialState() {
        return {
            projectInfo: {
                name: "",
                version: ""
            },
            menus: [
                {title: "", icon: "", action: ""}
            ],
            links:  [
                {title: "", icon: "", action: ""}
            ]
        };
      },

    componentDidMount() {
        this.stateSubs = navBarStore.subscribe( (stateData) => this.setState(stateData) );
    },

    componentWillUnmount() {
        navBarStore.dispose(this.stateSubs);
    },

    render() {
        return (
            <div>
                <NavBarMenus projectInfo={this.state.projectInfo} menus={this.state.menus}></NavBarMenus>
                <NavBarLinks links={this.state.links}></NavBarLinks>
            </div>
        );
    }
});

module.exports = StudioNavBar;

Simplified application state management is a big plus for our heavily data driven application, and runtime performance is another benefit from Virtual DOM’s implementation. React has big advantages over Polymer for data integration.

Performance

Although many aspects can impacts application performance, like optimization, build, web server configurations, etc., here we only focus on the factors that directly contributed from the library, leave out other application and server specifics. Fast loading and efficient rendering is critical for data driven large-scale applications, a component-based web application’s performance should be:

  1. Consistent high performance cross browsers, including loading, rendering and updating
  2. High performance for large collection rendering and event handlings
  3. Flexible and efficient application state management for deterministic quick view updates

My test app was built with two branches, one for Polymer 1.1.5 implementation and another with React 0.14.0 and Flux, here is some performance numbers on localhost: (Firefox v41.0.2, with webcomponents-lite.min.js polyfill)

 

Loading

Scripting

Rendering

Painting

Other

On-load

JS Heap

Nodes

 

Polymer v1.1.5

29.63ms

859.12ms

26.95ms

2.39ms

148.44ms

1,570ms

30,838,896

13,771

 

React 0.14.0

19.18ms

919.49ms

34.33ms

2.92ms

174.56ms

1,710ms

56,132,296

16,031

 

Here is the interesting read:

  • Polymer app loading is 54.48% slower than React. My component packaging may contributes to the slowness: all 3 components are downloaded on demand (not using Vulcanize), and another round trip for polyfill;
  • Once downloaded, Polymer app is slightly faster than React for initial rendering. React app is 7.02% slower in scripting, 27% slower in rendering, 22% slower in painting and 8.9% slower for on-load scripting;
  • React app has significant bigger memory footprint than Polymer’s: 82.02% bigger in JavaScript heap size, this is mainly the result of Virtual DOM implementation;
  • (Notes: I didn’t list re-rendering (trigger by large collection update in model) performance number, technically React’s Virtual DOM is quite efficient, but I’ll leave it to another article discussing replacing Flux with Reactive Extensions, like RxJS).  

It worth noting that React does support isomorphic JavaScript if initial loading performance is critical, while Polymer doesn’t support server side rendering.

Vulcanizing in Polymer app build will improve loading speed, and a consistent dependency management and packaging strategy can greatly impact Polymer based app loading performance. In our use case, re-rendering (view update) performance is more important than initial loading, I’ll follow up with another writing when all tests are done with a React + RxJS experimentation.

Programming Model

With or without components, an ideal programming model for web development should be:

  1. Simple & Powerful: avoid unnecessary complexities, like digest loop, dependencies injections, etc., clean APIs with great capabilities
  2. Efficient & Secure: rendering efficiency with simplified state management and DOM manipulation with secure content built-in
  3. Functional & Reactive: this is really nice to have, since the DOM API is imperative

Polymer makes building extensible web easy, its innovation really relies on extending current well known and standard based web paradigm, its custom element based component model can not be more intuitive compare to React JSX. As a component developer, the continuation and consistency also applies to programming model, it flows the classic imperative DOM API with direct DOM access.

However, because JavaScript is not sandboxed within Shadow DOM, nothing really prevents component’s JavaScript from reaching outside to operate on hosting document. This non-scoped JavaScript imperative programming model is like a missing feature in current Shadow DOM spec.

Unlike Polymer’s data binding and imperative programming model, React enables and promotes functional programming that enabled by Virtual DOM.

Virtual DOM has high performing diffing algorithm to identify the DOM element(s) that actually need change, application can avoid expensive and slow direct DOM manipulations. Therefore, application state data can be immutable, no complicated state management and tracking is needed, whenever data changes, we can rely on React to figure out the most efficient way to update the DOM.

Furthermore, no data binding is required in React.js, both JavaScript and CSS can be in-line as well.

It took me some time to understand these “non-best-practices” with React, it turns out to be the most compelling innovations from React.js: to make functional and state-less DOM programming possible on top of traditional imperative DOM API. No data binding is absolutely needed, simplifies state management, all because application can totally delegate DOM manipulation to Virtual DOM.

Clearly, React wins programming model considerations. It opens the doors to utilize immutable.js and Reactive Extensions for simplified while powerful data and state operations.  

Production Readiness

When we improve application architecture that is already in production, we’re really looking for product ready solutions, the major considerations are:

  1. Stability: we’re looking for cutting edge solutions that relatively stable to support our production release cycle
  2. Adoptions: the more highly scaled commercial sites adopt it, the more solid it is
  3. Community Support: active open source community support, together with ecosystem’s development (add-ons, helpers, utilities, tools, etc.) are also very important aspects

Here is the list of sites that is using React.js, and here is a curated collection of web apps and sites that are using Polymer. Although both have broad and active communities around them, React.js has even more boarder adoptions among some high profile sites, like AirBnB, BBC, CloudFlare, Dropbox, Flipboard, Netflix, Salesforce, Uber, Yahoo and of cause, Facebook.

From GitHub, React.js has more watchers, stars and forks than Polymer (at the time of this writing: 2,274, 31,194, 4,892 vs 987, 13,204, 1311). And Polymer has 74 releases compare to 34 releases from react.js. React.js is more production proven than Polymer.

Beyond production readiness, future proof is also our consideration. With W3C standard body’s back, Polymer has the most promising future. However, in terms of browser native support, adoptions, maturity and broader community support and ecosystem, both standard body and Polymer has some works to do down the road. 

Wrap up

No matter how thought provoking this technology exploration is, at end of the day, we have to select one for our next phase of development. When we take a step back to look at these two solutions holistically, React.js has clear advantages over Polymer at this point (React.js v0.14.2 vs Polymer v1.2.1).

W3C Web Components standards and Google’s engineering power makes Polymer the most future proof solution for component based web application, it extends web platform’s evolution while changes how web application is developed and how API is exposed profoundly. Although I believe Web Components is the future, with or without React, until components dependency management is effectively managed (via HTML Imports), and JavaScript is sandboxed (like CSS and HTML template), Shadow DOM would not have wide browser native support.

React.js’ Virtual DOM is quite disruptive, its ecosystem (add-ons, reactive extensions, immutable.js, unidirectional data flow architecture, etc.) are growing and expanding, with the success deployments on Facebook and Instagram, it’s more stable while keeps evolving at the right pace, more feasible for production use.

Although there are some efforts to combine the best of both world, (for example, to implement React component as composition of smaller web components, or using web component as top level composable component interface and implements them with React, etc.), I’d leave these cutting edge further exploration to the pioneers, our next phase production development will adopt React.js component and programming model.

For now, my answer is: we should start with React and prepare to be web components ready.

 

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)