In 2016, Chợ Tốt rebuilt frontend web application using React & Redux for business expansion. Likewise, we also decided to step by step migrate current services to Microservices Architecture to scale up. To boost the speed of our web app, we split our business into small chunks and each one is an application. Although doing this way reduces the complexity of the business, we faced some challenges:
- Consistency: many web apps mean the more work to keep consistency across product features.
- Reusability: many web apps mean we need a way to organize and share common components.
One particular feature on our website that has all these challenges is the “Header& Footer”. Typically, the “Header & Footer” is used by all products at Chợ Tốt and it contains links to important pages that potential users will often visit before making a purchase or inquiry.
This topic talks about 3 main points of how we built the “Header & Footer”:
- Build process
Before deep dive into the detail, let’s take a look at the principles we keep in mind when we develop this component.
We keep in mind the “Simplicity & Efficiency” of the components’ interface. They also help developers integrate easily.
“Simple can be harder than complex: You have to work hard to get your thinking clean to make it simple“ — Steve Jobs.
Let’s look at the example of 2 components code as shown:
It’s easy to see that if we want to use the component on the left side, we have to read documents carefully to understand the meaning of each prop and what if there is no document?. Developers often dive into the code to see how it works and makes assumptions.
On the other hand, the right side there are only 3 props they need to care about and the name of properties are declarative. With that even though the developers don’t read the document, they still understand it.
A library is like a product of developers. If it has a good UX (How easy to use it), other developers will be happy to use it.
Extensibility & Testability
With the business expansion, there are a lot of features integrated into the “app-wrapper”. We follow the “Single Responsibility Principle” to design the code base to make it easy to extend and test.
Less depending on libraries.
Using many libraries to develop is unnecessary. The more libraries we use, the bigger size of JS file is. It inadvertently slows down the web page loading. Because Chợ Tốt products reply on React & Redux, we decided to keep only those libraries to develop “app-wrapper”.
The app-wrapper divides into 2 zones
1.1. What is the Component zone?
The component zone contains web components need to render the Header & Footer such as:
The Microservices Architecture is good for reducing the complexity and unnecessary code of the particular feature under construction. However, with many new features are continually released every 1–2 months and each one has its own “entry point” when we release a new feature, we need to add “entry point” to the “app-wrapper” and publish it with a new version. Then we go to each project to upgrade the “app-wrapper” in order to have an entry point link to this feature.
Additionally, we have a lot of web applications and we have to make sure all of them have the latest version of app-wrapper. What happens if we miss one? It could affect user experience.
Because of these reasons, we decided to develop an API to manage entry points, the “app-wrapper” makes a request to fetch a list of menu items from the server and render.
By doing this way, when we have a new entry point, we only update the entry point at API endpoint and do it once.
1.2. What is the Extension zone?
The “app-wrapper” has some social features such as “Receiving Chat’s Notification”, “Displaying Announcements”. Those features require a lot of logic and big libraries such as Socket I/O. If we put all the code inside the “app-wrapper”, here what we are going to deal with:
- The code base is going to be huge.
- It hurts “Single Responsibility Principle”. Since the “app-wrapper” take care of displaying Header & Footer. It doesn’t need to take care of other business.
- It unnecessarily becomes more complex.
We develop an area called “Extension” zone which allows loading asynchronously the third party services.
We got the CDN links to services “Receiving Chat Notification” & “Receiving Announcements” (each service is a specific project and the output is a CDN link). Then we only need to register the link to “Extension” zone and let the Extension do the rest.
By doing this way, we achieve some benefits:
- Delegating all the logic of third-party services to CDN links helps to separate the logic.
- Reducing the size of the main JS file.
- Simplifying code base which helps other engineers easy to improve.
The “app-wrapper” contains the styles itself. Choosing a way to manage CSS is one of the hardest problems. There are 2 approaches:
JS exports CSS to a JS module. This means we could import CSS directly to JS code.
This is the original method. All CSS is bundled to CSS file (style.css).
Since all products at Chợ Tốt use JS to develop and the “app-wrapper” is a library which needs to provide less configuration for developers to integrate to main apps. For this reason, we decided to choose “CSS-in-JS” approach to manage styles of “app-wrapper”.
There are some libraries help to apply “CSS-in-JS” approach such as “styled-components”, “JSS”,… However, we have various teams and each team has its own style. Some use “CSS-in-JS”, some use “CSS file” to develop web apps. So the question is “Is there any method that could fit all?”. And we came with 1 solution that instead of using CSS-in-JS frameworks, we choose “Template strings” feature of ES6 to develop CSS.
After using this approach. Everything works pretty well. However, we encountered 2 big problems in the production environment.
- CSS is not minified.
- CSS does not contain prefixes for old browsers.
After running the build command to compile ES6 to ES5, we run another script to add prefixes & minify the CSS.
We chose Gulp to customize the build process by adding the post-build stage with 2 tasks:
It means after we ran the build command successfully
npm run build
The post-build command is automatically executed. Below is the result applied this method.
III. Build process
The build process is the way we convert JS code from ES6 to ES5 by using Babel CLI.
The build process has 2 stages.
- Stage 1 (build): It uses babel CLI to compile the ES6 code to ES5 code.
- Stage 2 (postbuild): It runs gulp tasks to minify & adds prefixes to CSS string from the build directory of stage 1.
After we finish the build process, we version the package and publish to private npm registry. All the projects only need to install a newer version of the package and enjoy.
We have just described the detail of “How we built the Header & Footer”. Now let’s have a quick look at the usage of the app-wrapper component.
Currently, the app-wrapper component is used by all the Chợ Tốt’s products.
We solved the 2 problems mentioned at the beginning of the post: “Consistency & Reusability”.
Besides that, we could bring the “app-wrapper” to the next level to become a common library.