FC-0007 - Piral Prototype Documentation and Notes

This document provides an overview of the Open edX Piral Shell Application, which is an implementation of the Piral Framework that addresses the requirements outlined in FC-0007. The complete prototype showcasing the use of piral-core to federate open edX MFEs is available at the following GitHub repository: https://github.com/hammerlabs-net/frontend-app-shell

Overview:

The prototype comprises a simple Piral shell instance which includes two Piral plugins that extend the Piral API, and a mock pilet feed service as defined in the Piral Specification. The shell implements the lifecycle methods of the open edX frontend-platform library and exposes the endpoints of the library to Pilets (microfront ends - MFEs) loaded by the shell.

Additionally, the prototype includes modified versions of the Open edX Learning and Account MFEs, refactored to build packages according to the Pilet Specification. It also features modified versions of the Open edX Header and Footer components, now built as pilets.

Minor modifications were made to frontend-build, paragon, and frontend-platform for compatibility purposes. As we explore further, it is worth discussing how these existing projects can best align with the Piral model moving forward.

Open edX Piral Shell

The open edX Piral shell is a container application that relies on the piral-core@1.0.1 libraries. The shell fulfills several requirements for the proposed solution, including:

  • MFE composition for domain-specific applications like LMS and Studio.

  • Universal routing between loaded MFEs.

  • Common user experience elements and components such as headers, footers, and navigation.

  • A streamlined developer experience.

  • Common management of dependencies (e.g., React).

  • Common runtime configurations for important concerns such as internationalization, Redux, and logging.

By leveraging the Kras server proxying library, which is integrated into Piral, the shell implements an MFE Feed Service that demonstrates how different configurations of pilets can be loaded into the shell. A simple change in the "Pilet Feed" can load an entirely different set of pilets into the shell, allowing the same Shell to host various Open edX applications such as Studio or LMS, or to offer alternative brand UX (edX vs open edX), or even to provide alternative MFEs suitable for specialized deployments. Moving this configuration to an external service, as recommended by Piral, would provide a highly configurable and flexible solution for Open edX deployments at run time.

This Piral instance leverages the Piral plugin mechanism to deliver a custom plugin that showcases the ability to address UX and UI consistency issues across MFEs while significantly improving the capability to deliver alternative layouts and designs to Open edX audiences.

The Layout Plugin, inspired by an example in the Piral documentation, allows the visual components of the shell (such as pages and components) to be defined by pilets rather than the shell itself. This decouples branding, look and feel, and common navigation elements from the features and function of MFEs, making them lighter and easier to develop and update as the platform and its deployments evolve. In this prototype, we offer two different layouts that can be switched via a URL query parameter (?alt=layout).

Under the hood, the Open edX Piral Shell offers significant advantages to developers and deployers of the platform.

This prototype showcases Piral's ability to simplify MFE development for Open edX. In addition to factoring out common navigation and display elements like footers and headers, developers can now develop and debug their MFEs by simply declaring their dependency on the Shell package itself. The ubiquitous npm start command works both from the Shell and any of the two MFEs or component libraries included in the prototype. This allows MFEs to start up the entire Shell for debugging, greatly simplifying the development environment and helping identify compatibility issues early in the process.

Sharing and managing dependencies across MFEs is one of Piral's most advertised features. Piral uses the importMap standard to handle shared dependencies from the shell to Pilets. In this prototype, the following dependencies are shared from the Piral Shell to MFEs: react, react-dom, react-router, react-router-dom, @edx/frontend-platform, @edx/frontend-build, and @edx/browserlist-config. MFEs that depend on the shell can declare peer dependencies to these libraries, thereby inheriting the dependency declared by the shell.

A notable outcome of the Piral model is the consolidation of configuration for common concerns such as the Redux store, environment variables, and Open edX frontend platform.

Redux is now centrally managed by the Shell, and Pilets use Redux Dynamic Modules to load and unload their reducers and enhancers. Similarly, many common environment variables can be declared once by the shell and shared across all MFEs loaded by that shell.

The Open edX frontend-platform library deserves special mention. This prototype includes a custom Piral Plugin that wraps and exposes many of the methods of frontend-platform (e.g., getAuthenticatedUser(), getAuthenticatedHttpClient(), and getConfig()) to Pilets. Pilets no longer have direct access to the initialized instance of the platform's services, as the initialization is encapsulated within the Shell. Going forward, we should assess the ongoing need for MFEs to declare a dependency on frontend-platform and explore the option of merging or encapsulating it entirely within the Shell and exposing it via the Pilet API (as initiated in this prototype).

MFE Conversion

One of the goals of this prototype was to gauge the effort required to convert existing Open edX MFE to Pilets, and whether a conversion could happen in stages.

The required changes to existing MFE’s are minimal (CAVEAT: this is evaluated in the context of converting the Account and Learning MFEs and the Header and Footer components. Other MFE’s may differ).

  • index.jsx the main entry point requires refactoring. Previously MFE’s were responsible for initializing and responding to events from frontend-platform. In the new paradigm, the Shell manages interactions with the platform while MFE’s must implement the Pilet lifecycle method setup(piralApi). Instead of setting up routing and routes to components, Pilets register their components directly with the Shell via the registerPage(), and registerExtension() apis. The APIs can be extended to offer support for additional components (for example paragon components extensively used by existing MFE so that MFE’s can share a single definition of this important library, or alternative ones as the need arises).

  • frontend-platform exported functions and objects now need to be wrapped and exposed from the Shell (where it is initialized) via the piral API. All uses of stateful methods previously imported from frontend-platform must be refactored in order to access the methods as exposed by the API. In the Learning MFE, we implement a simple helper object that is created during the setup() lifecycle method. (frontend-app-learning/src/data/api.js)

  • redux store is now created centrally by the Shell. Instead of being responsible for their own stores, Pilets now simply wrap their rendered components in the DynamicModuleLoader HOC available in the redux-dynamic-modules library.

  • The build process for MFE’s differs when bundling for Pilets. This prototype includes two new webpack configurations - one to bundle the shell itself and one to bundle MFEs. The second one in particular is very similar to existing Open edX development configurations but it removes the HTTP server as that is now handled by Piral. In addition, the make targets for these MFE’s have been deprecated in favor of the npm run build target which includes the Pilet packaging features to ensure compliance with the specification.

  • Package depedencies: Piral’s ability to share dependencies will lead to substantial refactoring of existing package.json files. This prototype demonstrates how dependencies can be shared, but it does not make an attempt at addressing what should be shared. Piral documentation includes thoughts that should be discussion starters as we explore what should be shared centrally. An important consideration to highlight and explore is the size of the Pilet packages. Piral suggests MFE’s should remain under 1Mb, but the Learning MFE alone sits at 25.3Mb when bundled. A combination of tighter dependency management and/or refactoring of features into smaller Pilets during the conversion process can help allay some of the concern around package size.