All articles

How we migrated Passbolt front-end to React

8 min. read

Passbolt team

Passbolt team

11 February, 2021

Any technology company must face a delicate dilemma.

On one hand your product must meet an appropriate time to market. Even if you propose the best product of the world, you can’t afford to be slow.

On the other hand one must control the technical debt. If you neglect your technical debt, the time to reach the next milestones will increase along with the product complexity. For instance, early architectural misconceptions can cause difficulties to integrate new features easily if not reworked. Hence, having an impact on your future time to market.

In some way, speed and technological debt represent the Yin and the Yang in IT. In Passbolt, we try to get the right balance between them. So, we felt that it was the right time to push the Passbolt frontend a step further.

Let’s discuss why and how.

CanJS was not enough anymore

Until recently, Passbolt frontend part was implemented by using CanJs. It really helped a lot at the beginning because it is quite easy to use with understandable concepts. We liked the modular and the clean Model View Controller approach. Believe it or not, when we started the project CanJS was still called JavascriptMVC. Moreover React and Vue didn’t exist yet and there were very few javascript frameworks to choose from.

Nonetheless, over the last few years, we felt that the original architecture was reaching the end of the rope with the more recent frontend developments. Implementing new features started to be a mess and upgrading dependencies more and more difficult!

Moreover, Passbolt is an open-source product with a significant community of users and contributors. Thanks to their feedback, we realized that CanJS was not synchronized with their preferences anymore nor with popular frontend practices in general.

In a word, it was time to change. Okay, but for what instead?

React as the perfect candidate

There’s no shortage of Frontend framework / libs choices. In the last decade there has not a month without a new “blazing fast” frontend library. The real three first criteria we prioritized in order to select the right tool were: maturity, efficiency and the “not-too-much-opinionateness”. After a lot of comparisons and an in-depth analysis, we opted for React as the perfect candidate. Let’s discuss the reasons through a number of technical points.

No deps please

One of our first priorities was to minimize the number of dependencies. Passbolt is a password manager, so security matters. Every single external piece integrated to the project must have been well scrutinized by us or a larger community of security researchers. So the less the dependencies, the better.

Also, we needed mature, flexible, tiny footprint tool with a light learning curve. We didn’t want a framework to manage every aspect of the application, but rather one that focused on a few things and did it well. Some amazing frameworks such as Angular would not fit our purpose for instance: too many dependencies and a strongly opinionated solution.

On the other hand, React is definitely the perfect candidate for that. VueJS could have been another one but the team was just more equipped with React in regard of their backgrounds. For example we already had a chance to work with React when collaborating with Thomas on the Mailvelope extension (another great web extension that allows you to encrypt your emails using OpenPGP)

While one can use the amazing number of open-source React community contributions, one can also opt to only use the lightweight core library and do the job perfectly. This is exactly what we’ve done. You will only find the react/ react-dom and react-router as dependencies. One exception is the use of the react-list which proposes a better user experience in case of a significant number of passwords in your Passbolt instance.

Hook or Be Hooked ?

Quickly, the question of whether using React Hooks was put on the table. How to deal with new trendy concepts such as Hooks. Should we directly embed this more functional approach and accept its lot of uncertainties or trust a road that has been more traveled such as the oriented-class approach?

Well, if years of developer experience teach you something is that taking a wise decision begins by doubting everything and admitting you know few things. First, every magic piece comes with a (future) price. So, don’t let yourself be abused by magic or popular effects at first. Second, your priority concern should be the team, not the technology.

So, to the question “Is everybody in the team comfortable with the use of Hooks and its immediate benefit for the project?”, we first heard a silence. Then a few discussions were needed to conclude that no, the project team is not comfortable with it right now.

It was a tough decision not to go for the excitement of a trendy modern approach and stick to less “sexy” standards, so we decided to adopt a future-proof approach so that we’ll always have the freedom to change it in the future if needed. We approached the React migration such as that tomorrow we can include React Hooks (if needed) the smoothest way. To prepare for that, it’s a matter of architecture choices which we’ll discuss in the next section.

React Context

To the core of the current front-end philosophy are components. A modern front-end application is basically a tree of components. Behind this simple idea hides a first crucial challenge: how to define our tree of components such that it remains future-proof?

We made a deep use of React Context with this simple idea: separate pure UI components (or what we called “dumb” components) with the business logic components defined as React context (providers).

The definition of React context was guided by the Passbolt business entities. For instance, specific contexts can be found related to the password workspace, users workspace or the administration part of the application. They are the keepers of the application state, shared among the different UI components. These latter ones are context consumers in React terms.

In Passbolt, the React contexts are used as publisher / subscriber pattern’s implementation. The context provider allows to store and change the dedicated parts of the application state while the React consumers, mainly the dumb components, are provided by this state information. This way, the dumb components can react to any state’s update and, for instance, refresh their view accordingly.

Besides, a useful characteristic of the React context is the ability to separate the definition of a context from the providers of this context. The context provider is based upon the definition of the context but it can override with its own interpretation and implementation. Suppose a context that stores and manages a set of passwords, for instance {passwords: [], onPasswordUpdated: () => {}}. We have the capability to define two (context) providers of this and implement the function field onPasswordUpdated in two distinct ways. In software engineering, we would probably call this the bridge pattern. In passbolt, we applied this pattern to share the same React context in the Frontend API part and Frontend browser extension part. In the near future, we intend to explore how we can make a better use of this.

All in all, we intended to approach a pure state management approach such as Redux, Mobx, useReducer hook, and so on by decoupling UI reaction from its resolution.. This is something we started to do with the AuthenticationContext for instance.

Source organization

Another point of focus was the way to organize our source code through folders. We wanted it business-oriented and, for that, use the recommendations one may find in Angular for instance.

The structure of folders are 3-level scaffolded and follow these rules:

  • The top folders represent business entities such as passwords, users, folders
  • Inside these entities folders, the components folders related to the business entity.

We force ourselves to name these folders to the business feature it is supposed to cover. We use a “<Verb><Object>” naming convention to highlight the related action.

  • Finally, in each component folder, we find its related JS(X) file using the folder naming.

This way, the developer knows exactly that any other source code file in this folder exists and is used as part of the main feature component and is not shared somewhere else in the project.

Example of component with the associated page, data and test

Micro-frontends despite ourselves

By essence, the Passbolt front-end is split into two parts: the part served by the Passbolt browser extension and the part served by the server when the browser extension is not installed yet (what we called the API front-end). The whole enchiladas forms the Passbolt front-end which must appear as smooth as possible. This is typically the micro-frontend approach.

Using React to embrace this approach is very straightforward. The same page can have several entry points and so different micro-frontends. For instance, Passbolt authentication is a standalone React app while the pure React “logged-in’’ functional part is another one. For security reasons, we favor iframes to organize our micro front-ends. Often considered as an “old school” approach, it still remains the safest way from a pure front-end point of view.

Behind the charming advantages of this approach, sooner or later you have to deal with issues. What if two micro-frontends need to communicate? How to handle route management especially in the case of iframes? Answering these questions would probably deserve a whole article. In the current state, we opted for an agnostic event-driven approach when it is strictly required. The browser extension is the conductor orchestra which receives the events from micro-frontends and dispatch to whom may be concerned the messages using the dedicated workers. It provides a more secure communication path while micro-frontends does not need to know each other.

Next Steps

With this frontend migration, the sky is now blue for the next upcoming features. It will enable the Passbolt team to ship features faster and increase the overall roadmap delivery velocity, which makes us all excited. In the near future, we also plan to bring improvements to this first React implementation

First, as discussed, we will focus to have a clean state management oriented use of React context.

Besides, we use the local storage as a pivot point between the React part and the Browser extension part. Even if it has interesting advantages such as a common source of truth, we need to rethink the way we use it especially in the case of local storage updates. For instance, the React part listens to local storage changes to update the views with fresh data. This is a good point but it over-engineers a bit the pure React frontend. We probably want to tend to something less opinionated and abstract a bit this strong relation with the local storage.

Another point of interest is the pagination of our fetched data. Even though it was historically not a priority for the first versions of the software, Passbolt has now grown in popularity and some of our users are using it with massive numbers of passwords, hence creating new challenges. Consequently, this priority changed from low to high. Several solutions are possible in such a scenario. Server pagination could seem to be the most logical one. Well, due to Passbolt particularities, that might not be the best choice. Another one is to use a browser local database such as IndexDB which has proven to be very helpful in case of offline consideration and handling queries in a serverless way. Anyway, you can expect soon even more UI fluidity in your Passbolt journey!

Last but not least, new coolest features are in the plan. With this React migration, we have a comfortable playground to free our mind. With your helpful suggestions, we are ready to push Passbolt to the next level.

So, stay tuned! And in the meantime feel free if you have questions to join us on the community forum or in the comments below. We will be happy to hear any feedback or ideas!

Article originally written by Karl Devooght