Jason Tseng

Apr 15, 2019

Building Shared Components

Building shared components bloomreach

Hey Babu! Shared Components!

In the world of CSS, there’s the concept of using only one stylesheet file to optimize web loading time, such that only a single request is needed when the client opens the page. As we moved into web-based application, the practice of writing a single file carried over.

Styles.css or themes.css are some of the most common names seen in projects and often come with hundreds, if not thousands, of definitions within a single file. As people come and go, this file tends to become a dumping ground and no one knows what’s in there anymore.

Aside from the occasional updates and modifications to individual items, the number of definitions in there that no longer have a corresponding DOM element, increases as time passes. But as the motto goes, "if it ain't broke, don't fix it." That single stylesheet usually turns into the technical debt that gets carried over and over.

 

At Bloomreach

During our initial phases, code duplication was a common phenomenon. For instance, our “common” stylesheet would exist in various forms across different dashboard projects.

After all sorts of mutation and cloning, it becomes impossible to track down what’s relevant and what's not.

From a couple of lines to more than 6 thousand lines, the work required to combine and clean up the files without causing user interface (UI) issues, is as daunting as replacing the columns in the basement of a skyscraper and hoping it doesn't fall down.

 

First Pass

During the rewrite of Bloomreach Dashboard in late 2016, one of the key objectives was to modularize our UI Components, i.e. the ability to import the specific stylings related to each component only.
Together with Webpack, React, and Sass, the entire br-theme file of 6000+ lines in Dashboard was deprecated.

This particularly enabled future engineers to find the related code/stylesheet way faster. This along with the introduction of Sass improved the readability and maintainability in a significant manner.

 

 

But...

Everything sounds great now, with an average of 70 lines per file, specific to the component that imported it. The styling becomes much more readable and manageable. But, what actually happened next was the issue crept into the component level.

Instead of cloning the stylesheet, now we clone the entire module into a new project. Then, as the project develops, so do those cloned components, taking on their own tweaks as new use cases occur.

This wasn’t too much of a concern initially. However, as we moved into the phase of combining dashboards, we had multiple copies of the same components, such as BrButtons.
With slight differentiation between each, it was a nightmare to maintain each individual component. A stricter and cleaner approach for reusable components became necessary.

 

Shared Components / Design Systems

Looking across the industry, we are certainly not the only company facing this issue. Atlassian, with some existing applications in React and migration of old ones into React, were stung with the pain of maintaining 45 different dropdown implementations. This is part of the reasoning for creating a unified internal design system that is codified and visualized.

The same can be seen across the board, such as with Airbnb and the well-known material design by Google. Many of these companies have gone through multiple rounds of evolutions.  

 

Our Setup

Combining with the effort of unifying dashboards, we started to host the in-house shared components library, Babu Library, on our internal NPM server. Beginning with migrating UI components from all Dashboards one by one into Babu. All the while making sure the library is generic and flexible and, at the same time, adheres to our standardized styling guidelines.

In addition, the dependencies that some components have on Redux need to be eliminated. This allowed for all components to be usable out-of-the-box, without a complicated setup.

 

As we roll on with the development, a couple more requirements became obvious:

  • The capability for future engineers and product team to view what’s available through demo pages, instead of reinventing the wheel

  • The ability to quickly understand how to integrate without having to flip through pages of code

  • Clear guidelines to follow for development “on” and “with” the library

  • Utilities that could be shareable aside from the UI based components, such as Javascript utility functions

 

Hurdles in the Process

 

Some of the speed bumps along the way include:

1. Bundle Size and Version Conflicts

Given the requirement of providing a visualizable demo page, we included an entry point to serve the Babu library as a standalone application, which requires the React library and a few dependencies to be bundled up during the Webpack build process.

However, although the requirement is met, the bundled library size became too big for the consuming applications. In addition, if the consuming application also imported libraries that were bundled already, we now effectively included two copies of such libraries in the consuming application build, which further increased the size of it, and can also run into version conflicts between the two copies.

After analyzing the webpack process and resulting library sizes with webpack-bundle-analyzer, by utilizing npm peerDependencies setting and webpack exclusion (config.externals), we were able to reduce Babu library size from 2.6 MB to 523 KB, which is further reducible by minification.

 

TreeMap packed with third-party libraries in the bundle.

 

TreeMap with only our own components.

 

2. Live Editing

To allow developers to quickly grasp how each component is used, the best way is to show the code required and how it is rendered. However, given the number of parameters different components accepts, there is no easy way to show it all in a simple static display.




By utilizing acorn-jsx, developers can then perform editing and rendering of the components live on the page. However, this only works for basic static components that simply renders based on the props passed in, not for components where states need to be maintained and changed based on user interactions or callbacks.
 

<Wrapper

         Component={BrEditableLabel}

         states={{value: 'text'}}

         handlers={{onSetLabel: function (value) { this.setState({value}) }}} />


To achieve this, a wrapper is developed to allow simulating the relationship of a consuming component and the shared component. It hosts the states and function callbacks, such that when a user interacts with the component UI, a callback is triggered to make updates to the states, which then feeds back into the component to trigger the update livecycle, for a fully interactive experience flow.
 

3. Continuous Development

During development, developers can symlink the Babu Library into their consuming project, such that any modifications on the library will be rebuilt and then triggers the build flow of the consuming application. This provides a much faster cycle from modification to seeing effects in the consuming application.

During testing, we now utilize tags to allow deployment of the library into a separate branch. This allows developers to publish non-production ready library without influencing the main branch, especially in cases where a full build is required for deploying to a server for QA purpose.
 

How is This Used Today?

The entire Babu codebase and usage can be thought of from three perspectives
 

  1. Product Team (Designers/PMs)
    Looking at the demo pages to explore the current set of components and its stylings, when designing new products/features or updating existing ones, as well as development for a unified UI/UX.

  2. Consuming Application Developers
    Looking mostly at the demo pages for how to use/integrate the components into your applications.

  3. Babu Library Developers
    Working together with the product team and engineering teams to update and enhance the library. As well as maintaining the demo page and develop best practices on the design system.

 

Simple relationship diagrams between Babu Library, demo pages, consuming applications, and related servers.

 

The Unfinished Path

Our original goal was completed after shipping the fully rewritten UI components. However, quite a number of improvements and additional requirements surfaced along the way, and we are looking to have these addressed as V3 in future. Some of the major items include:

  • Selenium Testing Framework

  • Flow Type Checking

  • Automatic documentation and demo page generations

  • Change-log / release documentation and notifications

  • Improved versioning and deployment flow

  • UI visualizable code change comparator

  • Improve and open source our Live Editing Feature for React Components (based on acorn-jsx).

  • Templates to enforce standardized styling and speed up new component development

 

Where did the Name Come from?

This is an interesting one. As we were releasing the library, the team urged me to choose a better name to replace the wordy “shared components library”. Without anything particular in mind or any maple trees outside my window (that was one of our internal Maple Dashboard got its name), this question was simply put aside as we have bigger challenges to tackle.

Until one of our global team meetings. I was reminded of my first ever trip to India back in June 2018, when I was asked for the name again. Aside from all the support and bonding, I am grateful for, one particular thing that stuck in my mind was when our teammate Shekhar referred me as Jason Babu.

I was confused initially - what’s a Babu? Is that a synonym for Baby? Wait...what? I didn’t know the culture in India is this open! After some inquiries, it turns out I was just hallucinating. The word “babu” is simply a suffix for showing respect, similar to the use of “Sir” or “Madam”.

So with my full appreciation, respect for the team, and the catchy name, we are proud to introduce to you to Babu Library 😊