Uniform Theming for Open edX#

The aim of this document is to explore the current state of theming in edx-platform and the MFEs, and to explore some immediate areas that need additional discovery or development to achieve the goals of this project.

This document will consider the goal of reduced maintenance burden and deeper customisation.

Hurdles#

The Open edX ecosystem seems to have settled on Paragon, which in turn is based on Bootstrap, for styling. Paragon is used by both the current “legacy” views in edx-platform and for the new MFEs. However, this has not eliminated all the complexity of theming and styling.

One major hurdle in achieving consistency across edx-platform and MFEs is that that edx-platform has a huge burden of legacy through previous styling systems. Even where Paragon is used, it is a much older version compared to MFEs.

Another issue is of consistency across MFEs and even within the same MFE. Properly evaluating what can and cannot be themed is pretty hard.

It is hard to evaluate what should happen if you change the primary and secondary colours for an MFE. What elements should adopt the colour and what shouldn’t? There are places where absolute styles are used such as bg-white instead of semantic ones such as bg-light. This means that altering the theme colour won’t affect such areas.

In some cases this might be on purpose, but this is something that definitely needs to be evaluated by a designer. Any resulting changes can be upstreamed. Additionally clients that want theming may have different ideas about what should or shouldn’t be themable.

Light Theming#

For light amounts of theming, changing the basic theme colours, there is already a good system in place with OEP-48. This allows us to provide our own theme package to the frontend, and it will use the variables or overridden styles from this package.

A rough look shows that it is possible to adapt simple-theme to work with MFEs, allowing us to reuse the existing Ocim UI to build styles for MFEs in addition to edx-platform. The limitation in how well the theme applies will then be about how consistently the css classes have been applied in the MFE.

It would be worth having someone with design experience develop a simple-theme configuration, apply it to all MFEs and get feedback on places they expect to have been affected by the theme but aren’t. Based on this feedback we can then upstream changes to MFEs to allow more consistent theming.

Another thing warranting further discovery is potentially consolidating theme files across MFEs. If MFEs separate custom styling needed by their components from base styling applied to all MFEs we can get a single themed css that can be replaced with greater ease, and even shared across MFEs for performance reasons.

Content Theming#

For more moderate levels of theming, the best mechanism currently is to replace entire components such as the header or footer with custom drop-in components that provide the same interface.

This is easily accomplished using npm install aliases, the same mechanism used above to provide a custom theme to MFEs. Using npm install aliases it is possible to install one package under an alias that is different from its original name. For instance the jquery package could be installed as jq, allowing it to be imported as jq instead of jquery.

The way this is useful is that it allows one to install one package as a stand-in for another. For instance, you could install a custom version of jquery available as jquery-next under the name jquery and it will be imported and used as jquery in the rest of the app without needing any code changes.

In the case of MFEs this allows us to install a custom header or footer component in place of the existing component, and it will work as long as we maintain the same interface.

As of this writing MFEs only use external header and footer components, so these are the only ones that can be replaced in this manner. It is worth examining what other kinds of components could be a good fit for being externalised in this manner.

It would also be useful to evaluate how individual components of a frontend can be replaced/overridden without needing to fork an entire repository. One area to explore here is the existing local module functionality of frontend-build. This feature allows using local overrides for existing packages for development, but could potentially also be adapted for creating overrides for individual components for production.

Recent versions of Webpack support module federation which would allow marking parts of the frontend as overridable, in which case an external module could be created that implements the same functionality as a replacement. This will need some discovery. This is likely the better way to go since it also potentially allows frontend plugins that are deployed separate from MFEs.

The better next step would be to evaluate how module federation can be used by MFEs to support replaceable components, and find candidates that should be made replaceable.

Extensive Theming#

For more intense customisation, if it isn’t possible with either of the two above approaches the best approach is probably to just create a new MFE that uses the standard MFE template so it can be deployed in a uniform manner.

Creating a new MFE is pretty simple, and it is quite possible to include/import code from an existing MFE, and link to it seamlessly. If such an MFE uses the standard theming approach for other MFEs it will also be automatically themeable, allowing it to fit in better with other MFEs.

Depending on how such an MFE is implemented (i.e. the APIs it uses) it can easily survive upgrades to the platform if it is using stable APIs. Using content theming approaches from above it should be possible to add links to existing MFE.

It could also be possible to replace one MFE with another as long as it follows the same URL conventions.

Next steps#

Based on what we’ve learned in this discovery, here are some of the things we can do next:

  1. Port simple-theme to an MFE branding package.
  2. Create a simple-theme configuration for MFEs based on an existing theme configuration.
  3. Create a sample deployment of an MFE using the above configuration.
  4. Get design feedback on how well the theme works, and what areas need to be updated.
  5. Investigate MFEs for additional reusable components that can be shared across them.
  6. Investigate module federation and how it can be used in MFEs.
  7. Evaluate areas in MFEs that are candidates for federation.