The goal of using a single frontend codebase for both Confluence Server and Cloud environments enables the development team to provide the same feature set and UI of Excellentable on both platforms. It also allows new features to be added to both platforms on a quicker schedule, as there is no need to develop separate applications. Having a shared UI and frontend codebase also became increasingly important to us as we realized that the Excellentable Cloud app was lagging behind Excellentable Server in regards to the features offered and due to the increased priority of Atlassian on their cloud framework of Confluence would increase the demand for newer features of Excellentable Cloud. So we set about to update Excellentable cloud in an efficient manner that would not put a strain on the development team to meet the demands of both Excellentable applications.


The first step in the process was ensuring that the REST API and Database structure were standardized on both versions of Excellentable. Due to the fact that Excellentable Server was the more mature offering as well as the ability for us to easily make database changes on the Excellentable Cloud app without affecting the users, the majority of the standardization involved updates on the Excellentable Cloud database. These updates include things such as updates to the naming conventions of variables, modifying variable types, and adding variables that were missing. One variable that was added was an instance variable which would respond with whether the Excellentable is being requested for Cloud or Server. Similar modifications were needed on the API side, we needed to ensure that the REST endpoints that were being used on the frontend were present and standardized.

Try Excellentable Cloud for Free

The next step was to remove all environment-specific functions out of what would be considered the core javascript files. This meant that any content containing references to AJS (Atlassian JS library for Server)or AP (Atlassian JS library for Cloud). Any function that required accessing the aforementioned Atlassian JS libraries was separated from the Core Excellentable JS files and added to environment-specific files, that is any function that referenced AP library was moved into a CloudOperations.js and similar actions were taken for functions referencing AJS. The references in the Core JS files were then modified to only be loaded based on an instance variable which would be obtained when an API request to retrieve Excellentable data is made.


One of the major changes that we made to give us the ability to have a shared codebase was the addition of Webpack. At its core, Webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph that maps every module your project needs and generates one or more bundles. by leveraging this bundling feature as well as ES6 imports. We were able to create specific bundles of the frontend code for Server and Cloud and even a Standalone version of Excellentable.

This was done using core webpack concepts, such as entry points, loaders, and plugins.

An entry point indicates which module webpack should use to begin building out its internal dependency graph. Webpack will figure out which other modules and libraries that entry point depends on (directly and indirectly). Through the use of entry points, we were able to specify different entry points for Cloud and Server.

 Loaders allow webpack to process other types of files and convert them into valid modules that can be consumed by your application and added to the dependency graph. Loaders were used for three purposes.

  1. babel-loader: This loader was used to transpile the JavaScript files. It also gave us the ability to use ES6 concepts such as classes and async/await.
  2. @atlassian/i18n-properties-loader: This loader gave us the ability to leave all AJS.I18n references in the JS code and use properties files to handle translations
  3. @atlassian/soy-loader: This loader gave us the ability to bundle all soy templates, as Google closures were the template handler that would eventually be used in both apps.

Once we were able to get separate bundles, it was just a matter of updating our build and deployment processes to add the required bundles to their specific repositories.

Want to learn more about this?

Contact Us