From 46bf1e8565baf939892eb08e0530f0bf3b2104e5 Mon Sep 17 00:00:00 2001 From: "Adolfo R. Brandes" Date: Mon, 13 Jul 2026 17:06:13 -0400 Subject: [PATCH] docs: OEP-65 reframe around build-time shell architecture Reframe OEP-65 to match the frontend-base reference implementation: build-time composition of apps into sites via a shared shell, replacing the dropped runtime module federation approach. Rework the glossary and ADRs to the new vocabulary (App, App Repository, Site, Shell, Shell Architecture, Slot, Widget), rename ADR-0003 to "Frontend Sites", remove the obsolete federation diagrams, and fix the "Composability" label typo. Co-Authored-By: Claude --- .../oep-0065-arch-frontend-composability.rst | 226 +++++++----------- ....rst => 0001-unified-platform-library.rst} | 59 ++--- .../0002-frontend-app-migrations.rst | 154 ------------ .../oep-0065/decisions/0002-frontend-apps.rst | 108 +++++++++ .../decisions/0003-frontend-projects.rst | 149 ------------ .../decisions/0003-frontend-sites.rst | 114 +++++++++ .../oep-0065/decisions/frontend-glossary.rst | 147 +++--------- .../oep-0065/module-project-architecture.png | Bin 587382 -> 0 bytes .../oep-0065/proposed-mfe-architecture.png | Bin 245632 -> 0 bytes .../0001-frontend-dependency-freeze.rst | 2 +- oeps/redirects.txt | 3 + 11 files changed, 377 insertions(+), 585 deletions(-) rename oeps/architectural-decisions/oep-0065/decisions/{0001-unified-platform-repository.rst => 0001-unified-platform-library.rst} (53%) delete mode 100644 oeps/architectural-decisions/oep-0065/decisions/0002-frontend-app-migrations.rst create mode 100644 oeps/architectural-decisions/oep-0065/decisions/0002-frontend-apps.rst delete mode 100644 oeps/architectural-decisions/oep-0065/decisions/0003-frontend-projects.rst create mode 100644 oeps/architectural-decisions/oep-0065/decisions/0003-frontend-sites.rst delete mode 100644 oeps/architectural-decisions/oep-0065/module-project-architecture.png delete mode 100644 oeps/architectural-decisions/oep-0065/proposed-mfe-architecture.png diff --git a/oeps/architectural-decisions/oep-0065-arch-frontend-composability.rst b/oeps/architectural-decisions/oep-0065-arch-frontend-composability.rst index 172d7d325..0cd1bd5a8 100644 --- a/oeps/architectural-decisions/oep-0065-arch-frontend-composability.rst +++ b/oeps/architectural-decisions/oep-0065-arch-frontend-composability.rst @@ -1,4 +1,4 @@ -.. _OEP-65 Frontend Composibility: +.. _OEP-65 Frontend Composability: OEP-65: Frontend Composability ############################## @@ -7,19 +7,20 @@ OEP-65: Frontend Composability :widths: 25 75 * - OEP - - :ref:`OEP-65 Frontend Composibility` + - :ref:`OEP-65 Frontend Composability` * - Title - Frontend Composability * - Last Modified - - 2024-04-12 + - 2026-07-13 * - Authors - * Adolfo R. Brandes * Pedro Martello * David Joy + * Brian Smith * - Arbiter - Adam Stankiewicz * - Status - - Provisional + - Accepted * - Type - Architecture * - Created @@ -31,9 +32,9 @@ OEP-65: Frontend Composability * - References - * `FC-0054 - Composable Micro-frontends Discovery `_ * `FC-0007 - Modular MFE Domains Discovery `_ - * :ref:`ADR 0001 - Create a unified platform repository ` - * :ref:`ADR 0002 - Frontend app migrations ` - * :ref:`ADR 0003 - Frontend projects ` + * :ref:`ADR 0001 - Create a unified platform library ` + * :ref:`ADR 0002 - Frontend apps ` + * :ref:`ADR 0003 - Frontend sites ` * :ref:`OEP-65 Frontend Glossary ` .. contents:: @@ -43,7 +44,7 @@ OEP-65: Frontend Composability Abstract ******** -This OEP proposes that Open edX micro-frontends (MFEs) adopt :term:`shared dependencies` and :term:`runtime module loading` - implemented via webpack :term:`module federation` - as an approach to improve the consistency, performance, and flexibility of the frontend architecture. +This OEP proposes that the Open edX frontend adopt :term:`shared dependencies` and build-time composability - implemented by composing our frontends into a shared :term:`shell` application at build time - as an approach to improve the consistency, performance, and flexibility of the frontend architecture. Motivation ********** @@ -111,56 +112,60 @@ The reality of MFEs is that while each application seeks to represent a single c Alternately, site operators may want to show different *versions* of MFEs to different users while keeping the rest of the app (header, navigation, other MFEs) unchanged. -There are alternatives to :term:`runtime module loading` and :term:`shared dependencies` which are used in some situations. These are not *rejected* alternatives, and so we include them here to help illuminate how their limitations are motivation for adopting runtime module loading and shared dependencies. +There are alternatives to build-time composability and :term:`shared dependencies` which are used in some situations. These are not *rejected* alternatives, and so we include them here to help illuminate how their limitations are motivation for adopting build-time composability and shared dependencies. Shared Libraries (Alternative #1) ------------------------------------ -Because each MFE is siloed from each other - both in repositories and at runtime - we can share code by extracting it into a library and having our MFEs depend on it. This creates more repository/dependency overhead, and is only useful at build-time, meaning we need to rebuild all MFEs using the shared library whenever we want to update it. +Because each MFE is siloed from each other - both in repositories and at runtime - we can share code by extracting it into a library and having our MFEs depend on it. This creates more repository/dependency overhead. + +The :term:`shell` proposed here is itself a shared library that every frontend depends on, so this proposal *builds on* the shared-library approach rather than rejecting it. The difference is what it adds. A shared library on its own only shares *source*: each consuming MFE still builds and deploys independently, so it bundles its own copy of that source, may pin a different version of it, and still renders as a separate, siloed page - leaving the `Bundle Size`_, `Dependency Maintenance`_, and `Full Page Refreshes`_ problems untouched. The shell goes further by *composing* those libraries into a single build and deployment, with one authoritative set of shared dependencies resolved once and rendered on a single page. Build-time package overrides (Alternative #2) ------------------------------------------------ NPM and ``package.json`` allow site operators to override dependency resolution by installing an alternate version of a dependency prior to build-time. This has historically been how we've allowed operators to override the header, footer, and brand. -The system is confusing, brittle, and only works at build-time. If a site operator needs different headers/footers/brands for different instances, this multiplies the number of required build processes for an instance. +The system is confusing and brittle. If a site operator needs different headers/footers/brands for different instances, this multiplies the number of required build processes for an instance. Frontend Plugins (Alternative #3) ------------------------------------ -`frontend-plugin-framework `_ gives us the ability to share components across MFEs as plugins, either at build-time (direct plugins) or runtime (iframe plugins) +`frontend-plugin-framework `_ gives us the ability to share components across MFEs as plugins, either at build-time (direct plugins) or runtime (iframe plugins). + +Direct plugins are the same kind of build-time composition this proposal is built on - in the :term:`shell`, apps and :term:`widgets ` are composed much as direct plugins are. On their own, though, FPF's direct plugins only inject a component; each MFE stays a separate build and deployment, with its own dependencies, header, footer, and page. The shell makes this composition the whole architecture rather than an add-on, so that composing also brings a single shared dependency set and one page. This proposal therefore absorbs and generalizes direct plugins rather than rejecting them. -Direct plugins create some flexibility, but couple our repositories' builds together, similar to shared libraries above. Iframes are good for sandboxing and isolating code, but they're a very inefficient way to compose a UI, especially given the consistency and user/developer experience concerns raised above. In a way, they exacerbate the problem even more. There's no way to do direct plugins via the MFE config API, since they rely on importing modules directly into the build. +Iframe plugins are good for sandboxing and isolating code, but they're a very inefficient way to compose a UI, especially given the consistency and user/developer experience concerns raised above. In a way, they exacerbate the problem even more. Specification ************* -Our approach centers on enabling :term:`runtime module loading` and :term:`shared dependencies`. Together, these two capabilities address the majority of the motivating problems described above (Consistency, User and Developer Experience, and Composability). +Our approach centers on enabling build-time composability and :term:`shared dependencies`. Together, these two capabilities address the majority of the motivating problems described above (Consistency, User and Developer Experience, and Composability). -We intend to enable runtime module loading and shared dependencies via `Webpack module federation `_. Further, we need to complement this new architectural approach with ways of *maintaining dependency consistency* between MFEs or we won't be able to realize the benefits of sharing dependencies. +We intend to enable these by composing our frontends into a shared :term:`shell` application at build time. Further, we need to complement this new architectural approach with ways of *maintaining dependency consistency* across frontends or we won't be able to realize the benefits of sharing dependencies. -Capability: Runtime Module Loading -================================== +Capability: Build-Time Composability +==================================== -The capability to lazily load content from independently built modules into the page - without iframes - solves many of the `Composability`_ and `User and Developer Experience`_ issues above. In particular, it gives us a way of composing UI elements from different MFEs/Domains dynamically at runtime without a :term:`host` application needing to know anything about the :term:`guest` at build-time. The two remain completely decoupled, save some shared runtime configuration. It also cuts down on the number of full page refreshes experienced by users. +The capability to compose content from independently developed apps into the same page - without iframes - solves many of the `Composability`_ and `User and Developer Experience`_ issues above. In particular, it gives us a way of composing UI elements from different domains into a single :term:`site`, and of extending pages via :term:`slots `, without each app having to build and deploy its own copy of the surrounding page, header, footer, and shared dependencies. It also cuts down on the number of full page refreshes experienced by users, since navigating between apps composed into the same site no longer reloads the page. -Not only do we want to load modules at runtime, but we want to *configure* which modules to load at runtime. This feature is called **runtime** :term:`remote discovery`, i.e., discovering which "guests" exist, perhaps from some API like the MFE configuration API, at runtime as the application is loading. We believe this is a hard requirement of our runtime module loading implementation because we want to avoid rebuilding "hosts" just because their module federation configuration changed. +Apps are composed into a :term:`site` at build time by importing them into the site's :term:`Site Config`. This keeps apps decoupled at the source level - a ``frontend-app-*`` repository need not know which sites compose it - while still producing a single, cohesive bundle in which those apps share one set of dependencies. -It's worth noting that adopting runtime module loading has a high degree of overlap with the capabilities of the `frontend-plugin-framework `_ (FPF) and is a natural extension of its feature set. +This composability rests on two ideas: apps are composed into the :term:`shell`, and each app can contribute :term:`widgets ` into named :term:`slots ` - its own, or those of other apps or the shell. This is how a page is extended or overridden without modifying the app that owns it, and it unifies and extends the capabilities of the `frontend-plugin-framework `_ (FPF), whose feature set this proposal absorbs into the :term:`shell`. Existing FPF plugins are converted to apps, much as MFEs are. -Advantages: Runtime Module Loading ----------------------------------- +Advantages: Build-Time Composability +------------------------------------ -* Reduces the frequency of full page refreshes. MFEs today are completely independent, so navigating between them means loading a completely new page (even if they share dependencies). -* Improves composability across MFEs/domains. We have no way to show more than one MFE on the same page today except by using iframes or by creating hard dependencies between MFEs at build-time by extracting 'shared' code into a new library, like `frontend-component-header `_, `frontend-component-footer `_, or `frontend-lib-content-components `_. Each of these increases our dependency maintenance burden significantly. -* Improves runtime extensibility by allowing us to configure where an MFE's code should be loaded from, rather than needing to build it in to an app. It dovetails nicely with the `frontend-plugin-framework `_ by providing us with a seamless, performant, and flexible way of extending our frontends without needing to rebuild the host. +* Reduces the frequency of full page refreshes. Independently deployed MFEs today mean that navigating between them loads a completely new page (even if they share dependencies). Apps composed into a single :term:`site` share one page load. +* Improves composability across domains. We have no way to show more than one MFE on the same page today except by using iframes or by creating hard dependencies between MFEs at build-time by extracting 'shared' code into a new library, like `frontend-component-header `_, `frontend-component-footer `_, or `frontend-lib-content-components `_. Each of these increases our dependency maintenance burden significantly. +* Improves extensibility by giving us :term:`slots ` and configurable apps, letting operators extend and override pieces of a page through their own :term:`Site ` configuration rather than by forking the frontends being extended. Capability: Shared Dependencies =============================== -Sharing dependencies between MFEs at runtime compliments runtime module loading. +Sharing dependencies between apps complements build-time composability. -MFEs have a number of dependencies which are common between them but which aren't actually shared at runtime in any way. The capability to share these dependencies - such as ``react``, `paragon `_, etc. - would mitigate a great deal of our `Consistency`_ and `User and Developer Experience`_ issues. +MFEs have a number of dependencies which are common between them but which aren't actually shared in any way. The capability to share these dependencies - such as ``react``, `paragon `_, etc. - would mitigate a great deal of our `Consistency`_ and `User and Developer Experience`_ issues. We expect the following packages - which are used in the vast majority of MFEs today - should all be shared between MFEs. @@ -205,43 +210,37 @@ Total size: ~2,087.2k unzipped (Note that these sizes are solely based on bundle Advantages: Shared Dependencies ------------------------------- -* When MFEs use the same version of a given dependency we see many benefits: significant reduction of developer cognitive load and context switching involved in working with multiple MFEs, fewer visual inconsistencies at runtime, and more. The `Approach: Webpack Module Federation`_ section has more details on how we foresee this working. +* When apps use the same version of a given dependency we see many benefits: significant reduction of developer cognitive load and context switching involved in working with multiple apps, fewer visual inconsistencies, and more. The `Approach: The Shell`_ section has more details on how we foresee this working. * Reduces runtime bundle size. We'll ship far less code to the client across a user's browsing session. -Caveat: "build-time" and "dependency maintenance" -------------------------------------------------- - -Note that "build-time" and "dependency maintenance" are not mentioned in the advantages above. The reasons for this have to do with how shared dependencies are resolved for :term:`modules ` at runtime, and are described in more detail in the `Approach: Webpack Module Federation`_ section below. - -At a high level, even if MFEs share dependencies, we want to preserve the ability for them to "fall back" to their own version of a shared dependency if a version already loaded on the page is incompatible with their own. To do this, each MFE builds and deploys its own version of all its dependencies in case dependency resolution determines they're needed. +Note: "dependency maintenance" +------------------------------ -This means that the build of a given MFE has to take time to bundle their own dependencies, and the repository still needs the dependencies present in the package.json file. Sharing dependencies doesn't help us much in regard to build-time and dependency maintenance. +Because apps are composed into a :term:`site` at build time, each shared dependency is bundled only once per site - there are no per-app fallback copies to resolve at runtime. This is what reduces bundle size and build overhead within a site. -Approach: Webpack Module Federation -=================================== +Sharing dependencies does not, however, reduce *dependency maintenance* on its own. Each ``frontend-app-*`` repository still declares its own dependencies in its own ``package.json``, and those version ranges must stay compatible with the :term:`shell` and with each other so they resolve to a single shared version at build time. Keeping them in sync is not something the build can do for us and is addressed separately in `Approach: Maintaining Dependency Consistency`_ below. -:term:`Module federation` is implemented as a `plugin for Webpack `_ that enables :term:`micro-frontends ` to be composed into the same page at runtime even if they're built separately and independently deployed. The pieces being composed are :term:`modules `. It lets us configure which dependencies should be shared between modules on a page and what modules a particular frontend exposes to be loaded by other frontends. +Approach: The Shell +=================== -More information on module federation beyond its webpack implementation can be found on its `dedicated site at module-federation.io `_. +Our approach is to introduce a shared :term:`shell` application through which our frontends are composed. The shell is responsible for initializing the application, loading shared dependencies, rendering the layout (including the header and footer), and composing one or more apps into a single :term:`site`. -If two modules require incompatible versions of a shared dependency, the second one loaded can fall back to loading a version it's compatible with from its own build. (see the link in step #4 below for details.) +Rather than each ``frontend-app-*`` repository building and deploying itself as an independent single-page application, these repositories become collections of composable apps. An operator assembles a :term:`site` whose :term:`Site Config` imports the apps it wants and configures them. The build produces a single bundle in which all composed apps share one set of dependencies. -In terms of Open edX MFEs, this means: +In terms of Open edX frontends, this means: -#. MFEs can continue to be built independently. -#. The Webpack build will include a manifest of which sub-modules the MFE provides at runtime. -#. `frontend-build `_ will specify - through its Webpack configs - a common set of shared dependencies to be used at runtime by all MFEs. -#. Webpack will intelligently resolve those dependencies at runtime, `taking into account each module's specific version requirements `_. -#. MFEs can dynamically load modules from other MFEs at runtime with Webpack handling hooking them up to the right dependencies. - -Because we already use Webpack, the work to add the ``ModuleFederationPlugin`` to our configurations is small and un-invasive (see proof of concept in the `Reference Implementation`_ section below). +#. ``frontend-app-*`` repositories are published as collections of composable apps rather than as standalone applications. +#. A :term:`Site Config` declares which apps a :term:`site` composes and how they are configured. +#. The shell's build tooling - which replaces `frontend-build `_ - provides a common set of shared dependencies used by all composed apps. +#. Because everything is built together, each shared dependency is bundled once and apps are wired to the same instance, with no per-app duplication. +#. Pages are extended through :term:`slots `, into which :term:`widgets ` are composed at build time. Approach: Maintaining Dependency Consistency ============================================ -This proposal fundamentally changes how we work with MFE dependencies, and will require us to adopt a more rigorous approach to ensuring dependency consistency and compatibility across MFEs. Independent MFE codebases must be kept in sync with regards to dependency versions or we lose the benefits of shared dependencies. Consistency doesn't come for free just by adopting shared dependencies at runtime. +This proposal fundamentally changes how we work with frontend dependencies, and will require us to adopt a more rigorous approach to ensuring dependency consistency and compatibility across apps. Independent app codebases must be kept in sync with regards to dependency versions or we lose the benefits of shared dependencies. Consistency doesn't come for free just by composing apps into a single build. -MFEs need a compatible version of the shared dependency to be available, otherwise they need to fall back to their own version. The process, tooling, and/or code organization necessary to provide that consistency is not something module federation can help with and needs to be addressed separately. +Apps composed into a :term:`site` must declare compatible versions of the shared dependencies, or the build will resolve them to multiple copies (or fail outright), negating the benefits. The process, tooling, and/or code organization necessary to provide that consistency is not something the build can do for us and needs to be addressed separately. We expect that this may need to take a number of possible forms. @@ -254,7 +253,7 @@ Further, we recommend that each Open edX release have a single supported major v We also need a process to migrate Open edX repositories through breaking changes in third-party dependencies. Ideally following the `Upgrade Project Runbook `_. -The :term:`module architecture` allows for migrations through breaking changes in third-party dependencies via the :term:`Linked Module` loading method, which will allow a frontend to run as separate, linked :term:`sites ` while migrating modules incrementally. +The architecture allows for migrations through breaking changes in third-party dependencies by running a frontend as separate :term:`sites ` connected by :term:`External Routes `, migrating apps from one to the other incrementally as they adopt the breaking change. Best Practices -------------- @@ -293,8 +292,8 @@ Out of Scope There are a few important - but tangential - concerns which are considered out of scope for this OEP and its resulting reference implementation. -* Implementation details of how module federation would be added in the `frontend-plugin-framework `_. -* How `Tutor `_ and other distributions will need to change to adopt module federation. +* Implementation details of the :term:`shell`'s slot and plugin mechanism. +* How `Tutor `_ and other distributions will need to change to adopt the shell. * Opinions on which dependencies we should adopt going forward (such as ``redux`` or other state management solutions) Rationale @@ -302,137 +301,75 @@ Rationale The majority of the concerns expressed in the `Motivation`_ section revolve around a lack of shared dependencies and the way in which MFEs are currently siloed from each other, preventing us from creating a more seamless, cohesive experience. -:term:`Module federation` specifically addresses these use cases exactly. It's right-sized to the problem at hand, can be accomplished with a minimum of impact on our existing MFEs, and can be done in a backwards compatible way (more on that below). +Composing our frontends into a shared :term:`shell` at build time specifically addresses these use cases. It gives us shared dependencies and composability while keeping our frontend code decoupled at the source level in independent ``frontend-app-*`` repositories, and it can be adopted incrementally (more on that below). -An approach to maintaining dependency consistency is essential to realize the benefits of sharing dependencies. Without it, we've accomplished very little even though we've added the capability. An approach to providing this consistency is not a prerequisite for implementing module federation, to be clear, but the *success* of module federation is tightly coupled to it. +An approach to maintaining dependency consistency is essential to realize the benefits of sharing dependencies. Without it, we've accomplished very little even though we've added the capability. An approach to providing this consistency is not a prerequisite for adopting the shell, to be clear, but the *success* of the shell is tightly coupled to it. Backward Compatibility ********************** -We intend to maintain backwards compatibility while migrating to and adopting module federation. We can do this by creating a separate set of Webpack configurations in `frontend-build `_ and separate build targets in converted MFEs; the footprint of module federation on "guest" MFEs is very small, requiring virtually no code changes in the application itself, and a few additional configuration options in the MFE's Webpack config to identify exposed components. For an example of what this looks like, please see the POC repositories in the `Reference Implementation`_ section below. - -Ultimately MFEs will no longer be responsible for initializing `frontend-platform `_ or rendering the header and footer. We will follow the `DEPR process `_ for retiring this code in MFEs once (and if) we make the module federation architecture required. +We intend to maintain backwards compatibility while migrating to and adopting the :term:`shell`. Existing, independently deployed MFEs can continue to run as separate :term:`sites ` reached via :term:`External Routes ` - a full page refresh - while they are converted into collections of composable apps one at a time. This lets us adopt the shell incrementally rather than in a single cut-over. -In the interim, MFEs will have both a Webpack configuration that exposes :term:`modules ` for consumption by other hosts as alternate entry points (to use Webpack parlance) _and_ the primary entry point which initializes `frontend-platform `_ and loads the header/footer. The POC below suggests this won't be a problem. +Ultimately these frontends will no longer be responsible for initializing `frontend-platform `_ or rendering the header and footer; the :term:`shell` provides these. We will follow the `DEPR process `_ for retiring that code as each frontend is converted and its apps are composed into a :term:`site`. Reference Implementation ************************ -A proof of concept has been created that demonstrates how Webpack module federation works with two Open edX micro-frontends based on the `frontend-template-application `_. The POC has several shared libraries (``react``, ``react-dom``, and `paragon `_), and loads a React component module from a guest MFE into the page of a host MFE. It supports hot module replacement during development, and runs on the two MFEs' ``webpack-dev-server`` instances. - -The POC repositories can be found here: - -* `frontend-app-mf-host `_ -* `frontend-app-mf-guest `_ +The reference implementation of this architecture is `frontend-base `_, which provides the :term:`shell` and replaces `frontend-build `_, `frontend-platform `_, `frontend-plugin-framework `_, `frontend-component-header `_, and `frontend-component-footer `_. -Proposed MFE Architecture -========================= +In frontend-base, an operator assembles their frontend as one or more :term:`sites `. A :term:`Site Config` (``site.config.tsx``) imports the apps to compose, along with the operator's own customizations, and the shell builds them into a single bundle. Pages are extended through :term:`slots `. -.. image:: oep-0065/proposed-mfe-architecture.png +The details of how operators structure, configure, and deploy their :term:`sites ` are described in the ``frontend-base`` documentation and the OEP-65 ADRs. -Diagram description: A diagram showing the proposed MFE architecture using Webpack module federation (`LucidChart source`_). Contains the shell application and a "guest" MFE. Shows how the `Shell`_ loads a manifest from MFEs (remoteEntry.js), and then uses that to load modules from the MFE, on demand, at runtime. The decision process around incompatible dependencies is shown, showing how an MFE that needs an incompatible version of a shared dependency loads its own version into the page as necessary - unless that dependency is a "singleton", in which case it will always resolve to the first version loaded. +Apps +==== +Each ``frontend-app-*`` repository exports one or more apps that a :term:`site` can compose. For instance, ``frontend-app-profile`` exports a profile app. Repositories may also export :term:`widgets ` to be composed into :term:`slots `. -MFEs and Modules ----------------- +The Shell +========= -Each of our MFEs will export a set of one or more modules that can be loaded by other MFEs or the :term:`Shell`. For instance, ``frontend-app-profile`` would likely export the ``ProfilePage`` component. Other MFEs may export their own pages, or perhaps plugins/widgets/components to be loaded by the `frontend-plugin-framework `_ via a "module" plugin type based on this implementation. +The :term:`shell` is the top-level frontend that composes a :term:`site`. It is responsible for: -Hosts and Guests ----------------- - -MFEs become either :term:`hosts ` or :term:`guests ` or both. A host is an MFE that loads runtime modules from a guest. A guest may itself act as a host to modules from another guest. For example, the :term:`Shell` is only a host and all MFEs are guests in the shell; further, some pairs of MFEs might have a host/guest relationship with each other. - -Shell ------ - -We will create a new "shell" frontend to act as :term:`site` which hosts all the modules from our ``frontend-app-*`` repositories. It is exclusively responsible for: - -* Initializing the application via `frontend-platform `_. +* Initializing the application. * Loading the default, expected version of all our shared dependencies. -* Rendering the "layout" of the application, including the header and footer. +* Rendering the layout of the application, including the header and footer. * Loading the brand. - -Like other hosts, it is also responsible for: - -* Loading all the manifests from the "guest" MFEs from which it intends to load modules. -* Using module federation to load the guest MFEs' modules on demand. - -Guest MFEs (not the shell) --------------------------- - -Guest MFEs that require a version of a shared dependency that's incompatible with the shell's version may load their own provided that dependency isn't a "singleton". Singletons in this context are dependencies that may only be loaded into the page once because they break if there are multiple instances active on the same page. React and `frontend-platform `_ are singletons, for example. - -If a guest needs to load its own versions of shared dependencies, this degrades the performance and experience of end users. MFE developers and maintainers should endeavor to use dependencies compatible with the version loaded by the shell. See `Approach: Maintaining Dependency Consistency`_ for details of how we might approach this. - -Converting the POC to a reference implementation -================================================ - -To convert this POC into a reference implementation, we need to minimally: - -* Create a new "shell" frontend to be the top-level "host" for all our other micro-frontends. -* Create module federation-based development and production Webpack configurations in `frontend-build `_. -* Modify the Webpack configuration to share the complete list of shared dependencies from the shell. -* Pick an existing MFE (or two) to convert to use module federation. Add build targets to these "guest" micro-frontends that can be used to build them in module-federation mode. -* Extend the Webpack configuration in the MFEs by defining what modules each "guest" MFE exports. We suggest that the package.json `exports `_ field be used to codify this list of exports, and that Webpack pull it in from package.json to configure ``ModuleFederationPlugin``. The format appears to be the same. -* Give "guest" MFEs a way of seeing their own config, since they'll be getting `frontend-platform `_ as a shared dependency from the shell, and won't be initializing it themselves. -* Unify our notion of a "plugin" in `frontend-plugin-framework `_ with a "module" loaded via module federation. There is power in merging runtime module loading and module federation into our existing plugin mechanisms via FPF. - -Secondary concerns include: - -* Ensuring nested dynamic modules work correctly. -* Ensuring static assets load properly in guest modules. -* `DEPR process `_ around the migration. -* Documentation on how to convert an MFE to use module federation and the shell. -* The configuration for loading the Open edX Platform's default MFEs. -* Documentation on how to do development with module federation and the shell. -* A decision on whether we use the MFE config API, env.config.js, both, or something else to supply the module federation configuration, whether it's one big combined document or whether each MFE has its own. -* How we sandbox and put error boundaries around dynamically loaded modules. -* How we manage breaking dependency changes across MFEs. +* Composing the apps declared in the :term:`Site Config`, and rendering :term:`widgets ` into their :term:`slots `. Rejected Alternatives ********************* -Vite Module Federation -====================== - -From a modernization and build performance perspective, `Vite `_ is an attractive replacement for Webpack. It is significantly faster to compile code than Webpack, and is generally more "user friendly" with a slightly higher-level API and configuration. It takes less configuration code to work with Vite, and it handles more file types by default (such as CSS/SASS, for instance). - -Vite has two notable implementations of module federation. `@originjs/vite-plugin-federation `_ and `module-federation/vite `_. - -Unfortunately, both of these implementations have limitations and drawbacks that make them unsuitable as a way of implementing runtime module loading for Open edX: - -- We consider runtime remote discovery to be a required feature of our module federation implementation. This is the ability to register remotes - servers which host modules - at runtime, not just at build time. We do not want to rebuild our "hosts" - whether shell or another MFE - when we want to add a new remote. We want to be able to get our remote configuration at runtime and register remotes with the system when the application is running. - -- Because Vite's development server does not transpile code and serves JS/TS code as native ESM modules, it is fundamentally incompatible with module federation and it does not support either module federation implementation. This means we completely miss out on one of Vite's best features. - -- The ``module-federation/vite`` implementation is effectively a proof of concept. It has very little documentation, and it's not clear whether it will ever be updated or maintained. We can't base Open edX's module federation on implementation on this. +Module Federation and Runtime Module Loading +============================================ -- ``@originjs/vite-plugin-federation`` appears more well supported, but similar to the ``module-federation/vite`` implementation, has limited documentation. +Earlier iterations of this OEP proposed enabling :term:`shared dependencies ` and composability through `webpack module federation `_, which would have let independently built and deployed bundles be loaded and composed at runtime. This was the original mechanism the OEP was written around. -Another related alternative we considered was to write our own module federation implementation using Vite for its build. This option significantly means there's even less documentation and no community support, and Vite lacks support excluding dependencies from its output bundle, similar to how `Webpack externals `_ works. We would need a mechanism like this to support a custom implementation. +We ultimately rejected it. None of the problems in the `Motivation`_ section - consistency, bundle size, full page refreshes, build time, dependency maintenance, or composability - actually require runtime module loading to solve; composing apps into a single build at build time addresses them all. Module federation would add significant complexity (a runtime resolution layer, remote discovery, fallback copies of shared dependencies, sandboxing and error boundaries around remotely loaded code) in exchange for a capability our goals don't depend on. Given that, we chose to simplify the architecture and leave it out. -For all these reasons, we've rejected a Vite-based implementation of module federation. If, at a later date, a more viable Vite implementation of module federation is available or we no longer have runtime remote discovery as a hard requirement, we might strongly consider migrating to Vite to be a great way of improving the performance and developer experience of our frontend builds. +This is not a permanent door-closing. If a concrete need for runtime module loading emerges - for example, a federated marketplace of apps that operators can install and load at runtime without rebuilding their :term:`Site` - module federation is the natural way to provide it, and we would revisit this decision at that point. Piral ===== A prior iteration of this OEP and discovery effort (`FC-0007 `_) came to the conclusion that we should adopt Piral, a comprehensive micro-frontend web framework, to address our concerns with the Open edX micro-frontend architecture. -After further investigation and review of our stated pains, observed deficiencies, hopes, and vision for Open edx micro-frontends, we chose to adjust course away from Piral. Piral solves runtime module loading and shared dependencies in a similar way to Webpack module federation - and can in fact use it internally - but does so in a more proprietary, opinionated, and opaque way, adding additional layers/wrappers around it. While Webpack is one of many bundlers available, it's also the defacto standard against which others are judged, and has wide industry adoption. Webpack module federation is a standard approach for composing micro-frontends. +After further investigation and review of our stated pains, observed deficiencies, hopes, and vision for Open edx micro-frontends, we chose to adjust course away from Piral. Piral solves composability and shared dependencies in a broadly similar spirit to our shell approach - and can in fact use tools like Webpack internally - but does so in a more proprietary, opinionated, and opaque way, adding additional layers/wrappers around it. We prefer an approach that builds on the tools we already use and stays closer to standard, widely adopted patterns for composing frontends. Piral is an impressive piece of software, built primarily by one individual, trying to solve a much broader problem than we have. Because of this, it brings along with it a great deal of complexity that we don't need and already have solutions for. Piral aims to be a complete toolkit for building web applications, including authentication, plugins, its own global state mechanism, extensions that provide ready-made UI components, etc. We need a mechanism to provide shared dependencies and composable frontends that can fit in with our existing ecosystem. Adopting Piral would likely involve significant refactoring of existing MFEs to fit into its framework and to turn them into "pilets", which locks us in to the Piral way of doing things. -It feels like our needs more closely align with the narrower scope of module federation, and that it's a more right-sized solution to our architectural problems. +It feels like our needs more closely align with the narrower scope of a shared build-time shell, and that it's a more right-sized solution to our architectural problems. Combining MFEs into 2-3 monoliths ================================= Folding our micro-frontends together into a few larger frontends (LMS and Studio, for instance) solves our need for shared dependencies in a different way - it just shares all the code so there's one set of dependencies for all of it. We could continue to rely on frontend-plugin-framework for cross-domain plugins, but "plugins" within the larger domain become a simple import from another part of the application. -This approach was abandoned because we still believe that MFE independence is a core need for our platform and we can't go back to a few monolithic frontends. MFE independence continues to allow independent teams to operate with autonomy, lets operators customize, build, and deploy MFEs independently as needed, and creates a more approachable platform for the community by keeping our frontends decoupled and focused. +This approach was abandoned because we still believe that MFE independence is a core need for our platform and we can't go back to a few monolithic frontends. MFE independence continues to allow independent teams to operate with autonomy, lets operators customize, build, and deploy their frontends independently as needed, and creates a more approachable platform for the community by keeping our frontends decoupled and focused. + +The build-time :term:`shell` approach can look similar at first glance - it, too, produces a single bundle - but it differs in a crucial way: our frontends remain independent ``frontend-app-*`` repositories that are *composed* into a :term:`site` at build time, rather than being folded together into a few large, co-located codebases. Combining MFEs into a monorepo ============================== @@ -474,6 +411,13 @@ The following related decisions modify or enhance this OEP, but have not yet bee Change History ************** +2026-07-13 +========== + +* Reframed the OEP around build-time composability via the :term:`shell` (as implemented in ``frontend-base``). Runtime module loading and webpack module federation - the originally proposed mechanism - were dropped from the reference implementation in favor of composing our frontends into a single build. The composable unit is now referred to as an "app", and operators compose apps into a :term:`site`. +* Removed the "Vite Module Federation" rejected alternative and the runtime-federation architecture diagram, both of which only made sense under the module federation approach. Added a "Module Federation and Runtime Module Loading" rejected alternative explaining why the originally proposed mechanism was dropped and under what circumstances it might be revisited. +* Renamed "Plugin Slot" to :term:`Slot` and introduced :term:`Widget` for the component composed into a slot. Existing frontend-plugin-framework plugins are converted to :term:`apps `, much as MFEs are. + 2024-07-31 ========== diff --git a/oeps/architectural-decisions/oep-0065/decisions/0001-unified-platform-repository.rst b/oeps/architectural-decisions/oep-0065/decisions/0001-unified-platform-library.rst similarity index 53% rename from oeps/architectural-decisions/oep-0065/decisions/0001-unified-platform-repository.rst rename to oeps/architectural-decisions/oep-0065/decisions/0001-unified-platform-library.rst index 7863c22c5..bd4de9a37 100644 --- a/oeps/architectural-decisions/oep-0065/decisions/0001-unified-platform-repository.rst +++ b/oeps/architectural-decisions/oep-0065/decisions/0001-unified-platform-library.rst @@ -1,7 +1,7 @@ -.. _Create a unified platform repository: +.. _Create a unified platform library: -Create a unified platform repository -#################################### +Create a unified platform library +################################# Status ****** @@ -11,16 +11,16 @@ Accepted Summary ******* -This ADR proposes combining a number of repositories into a single, unified frontend platform library as a migration strategy for implementing :ref:`OEP-65 `. This library is named `frontend-base `_. +This ADR proposes combining a number of repositories into a single, unified frontend platform library as a migration strategy for implementing :ref:`OEP-65 `. This library is named `frontend-base `_. Context ******* -OEP-65 proposes adopting webpack :term:`module federation` for Open edX :term:`micro-frontends ` (MFEs) as a means to enable :term:`runtime module loading` and :term:`shared dependencies` in the :term:`Open edX frontend`. The OEP lays out a series of changes necessary to enable these capabilities, which it refers to as building a "reference implementation" of runtime module loading and shared dependencies. +OEP-65 proposes building the :term:`Open edX Frontend` from independently developed :term:`apps `, all composed into a shared :term:`Shell` in one or more :term:`Sites `, as a means to enable build-time composability and :term:`shared dependencies `. The OEP lays out a series of changes necessary to enable these capabilities, which it refers to as building a "reference implementation". -This reference implementation is effectively a new underlying architecture for our frontend. This ADR refers to this new architecture as the :term:`module architecture`, as opposed to the historical :term:`micro-frontend architecture` that has existed prior to OEP-65. +This reference implementation is effectively a new underlying architecture for our frontend. This ADR refers to this new architecture as the :term:`Composable Architecture`, as opposed to the historical :term:`Micro-frontend Architecture` that has existed prior to OEP-65. -The bulk of the work to build OEP-65's reference implementation is related to the *library repositories* that MFEs depend on: +The bulk of the work to build OEP-65's reference implementation is related to the libraries that MFEs (as opposed to :term:`Apps `) currently depend on: * frontend-build * frontend-platform @@ -28,7 +28,7 @@ The bulk of the work to build OEP-65's reference implementation is related to th * frontend-component-header * frontend-component-footer -It will also involve the creation of a new :term:`Shell` to initialize the frontend at runtime and load existing MFEs as :term:`modules `. Prior to this decision, we might have considered putting the shell in its own repository as well: +It will also involve the creation of a new :term:`Shell` to initialize the frontend and compose :term:`apps ` into a :term:`Site`. Prior to this decision, we might have considered putting the shell in its own repository as well: * frontend-app-shell @@ -37,12 +37,12 @@ There are several related concerns which influence our strategy for migrating th Significant API changes ======================= -The creation of the shell and the move toward using :term:`plugins ` and module federation is expected to significantly change the API surface across our various libraries. +The creation of the shell and the move toward composing :term:`apps ` is expected to significantly change the API surface across our various libraries. ``frontend-platform`` --------------------- -As the shell takes responsibility for much of what ``frontend-platform`` provides to MFEs in the micro-frontend architecture, many of the APIs it exposes will become internal to the shell. We will also likely need to add new APIs for interactions between the shell and modules. +As the shell takes responsibility for much of what ``frontend-platform`` provides to MFEs in the :term:`Micro-frontend Architecture`, many of the APIs it exposes will become internal to the shell. We will also likely need to add new APIs for interactions between the shell and apps. ``frontend-build`` ------------------ @@ -57,7 +57,7 @@ The header and footer will be owned by the shell and need not be libraries any m ``frontend-plugin-framework`` ----------------------------- -We expect ``frontend-plugin-framework`` to be folded into ``frontend-platform`` to unify the two, as they will be intimately tied to each other going forward. We'll also be creating new APIs around loading plugins that support module federation. +We expect ``frontend-plugin-framework`` to be folded into ``frontend-base`` to unify it with the rest of the runtime library, as they will be intimately tied to each other going forward. We'll also be creating new APIs around composing :term:`widgets ` into :term:`slots `. ``frontend-app-*`` Repositories ------------------------------- @@ -77,59 +77,58 @@ We also feel the need to continue to modernize our library repositories by adopt Deprecation of the micro-frontend architecture ============================================== -As described in OEP-65, we're migrating from the :term:`micro-frontend architecture`, where each MFE is a standalone, independently deployed app, to a :term:`module architecture` where the MFEs are modules loaded into a common :term:`Shell` which owns the header, footer, initialization, and shared dependencies. +As described in OEP-65, we're migrating from the :term:`Micro-frontend Architecture`, where an MFE is a standalone, independently deployed site, to a :term:`Composable Architecture` where the MFEs become :term:`apps ` composed into a common :term:`Shell` which owns the header, footer, initialization, and shared dependencies. -One way or another, this is a paradigm shift that will involve breaking changes and migration work on behalf of community members. We would like to provide a clear a path forward for operators, developers, and maintainers to adopt the module architecture. We believe that a clean break and a cohesive and clear platform for modules is more approachable than a more granular deprecation of certain features and code in the existing library repositories. +One way or another, this is a paradigm shift that will involve breaking changes and migration work on behalf of community members. We would like to provide a clear a path forward for operators, developers, and maintainers to adopt the module architecture. We believe that a clean break and a cohesive and clear platform for modules is more approachable than a more granular deprecation of certain features and code in the existing libraries. Decision ******** -We will migrate our existing foundational library repositories to the new module architecture by creating a new, unified platform library - `frontend-base `_ - to act as the primary dependency of frontend modules in the module architecture. +We will migrate our existing foundational libraries to the new :term:`Composable Architecture` by creating a unified platform library - `frontend-base `_ - to act as the primary dependency of frontend :term:`apps `. -This library will include the parts of ``frontend-build``, ``frontend-platform``, ``frontend-plugin-framework``, ``frontend-component-header``, and ``frontend-component-footer``, as well as the new shell application that make sense for the module architecture, along with new features and capabilities necessary to implement our vision. We will also opportunistically fold in ``eslint-config``, which sees very little development but is its own source of dependency management overhead. +This library will include the relevant features of ``frontend-build``, ``frontend-platform``, ``frontend-plugin-framework``, ``frontend-component-header``, and ``frontend-component-footer``, as well as a new :term:`shell ` application, along with new features and capabilities necessary to implement the vision in OEP-65. We will also opportunistically fold in ``eslint-config``, which sees very little development but is its own source of dependency management overhead. This new library will be released as one npm package with the following responsibilities. Runtime library =============== -The library will export a subset of the APIs in ``frontend-platform``, along with ``frontend-plugin-framework``'s API and new APIs specific to the module architecture and the shell. This library will be a dependency of MFEs, as ``frontend-platform`` has been historically. +The library will export a subset of the APIs in ``frontend-platform``, along with ``frontend-plugin-framework``'s API and new APIs specific to the composable architecture and the shell. This library will be a (peer) dependency of frontend apps. Development tool configurations =============================== The library will provide base ESLint, Jest, TypeScript, and Webpack configurations. ESLint, Jest, and TypeScript will be similar to what ``frontend-build`` provides. -The webpack configurations will support a variety of build targets for the new module architecture, as well as application MFE configurations to enable us to migrate to the new unified platform library in a backwards compatible way. The new build targets include: +The webpack configurations will support a variety of build targets for the new :term:`Composable Architecture`, as well as app configurations to enable us to migrate to the new unified platform library in a backwards compatible way. The new build targets include: -* A production configuration suitable for deploying the MFE's modules to be consumed via module federation. -* A release configuration which will package those same modules to be released as an npm package for use as direct plugins. -* A development server configuration to serve the MFE's modules to be consumed locally via module federation, complete with hot module reloading. -* A development server configuration to serve the MFE's modules inside a shell as direct plugins. +* A production configuration suitable for building an :term:`App` into a deployable :term:`Site`. +* A release configuration which packages an :term:`App Repository`'s :term:`apps ` as an npm package for build-time inclusion in a :term:`Site`. +* A development server configuration to serve an :term:`App` inside a :term:`Shell`, complete with hot module reloading. These webpack configurations will be exposed via a function like ``frontend-build``'s '``createConfig`` so that they can be modified and extended as necessary by the MFEs. CLI tool ======== -Similar to ``fedx-scripts`` provided by ``frontend-build``, the ``frontend-base`` library will provide a CLI tool. This tool will expose commands to run the above webpack configurations, as well as other commands related to frontend projects. The rationale and details of frontend projects and these CLI commands are the subject of a future ADR. +Similar to ``fedx-scripts`` provided by ``frontend-build``, the ``frontend-base`` library will provide a CLI tool. This tool will expose commands to run the above webpack configurations, as well as other commands related to building and developing :term:`Sites `. The rationale and details of these CLI commands are the subject of a future ADR. Consequences ************ -This approach allows us to treat the :term:`module architecture` as an independent effort, as opposed to a set of features that need to fit in with the existing :term:`micro-frontend architecture`. We greatly reduce or eliminate the risk of regressions in libraries that the entire community relies on while we build a replacement which resembles and borrows code from them, but is otherwise significantly different. This division makes it explicitly clear which features are used by the old architecture and which are used by the new one. +This approach allows us to treat the :term:`Composable Architecture` as an independent effort, as opposed to a set of features that need to fit in with the existing :term:`Micro-frontend Architecture`. We greatly reduce or eliminate the risk of regressions in libraries that the entire community relies on while we build a replacement which resembles and borrows code from them, but is otherwise significantly different. This division makes it explicitly clear which features are used by the old architecture and which are used by the new one. -Further, it means we can migrate to the new architecture in a backwards compatible way by atomically migrating MFEs to use the new `frontend-base `_ library, again, without running the risk of destabilizing existing libraries with multiple breaking changes over time. +Further, it means we can migrate to the new architecture in a backwards compatible way by atomically porting MFEs to :term:`Apps` that use the new `frontend-base `_ library, again, without running the risk of destabilizing existing libraries with multiple breaking changes over time. We believe this is a reasonable migration strategy given the generally low rate of feature development in these libraries. We incur incrementally more maintenance burden in the interim while both sets of libraries exist, and a level of effort in absorbing any features or bug fixes in the existing libraries. However, we believe that will be offset by an increase in development velocity for the new library, a clearer deprecation process, less risk of regressions in existing code, and an easier mental model of how the architecture is changing. References ********** -* :ref:`OEP-65: Frontend Composability ` +* :ref:`OEP-65: Frontend Composability ` * :ref:`OEP-65 Frontend Glossary ` -* :ref:`ADR-0002: Frontend App Migrations ` -* :ref:`ADR-0003: Frontend Projects ` +* :ref:`ADR-0002: Frontend Apps ` +* :ref:`ADR-0003: Frontend Sites ` Change History ************** @@ -140,6 +139,12 @@ Change History * Document created * `Pull request #598 `_ +2026-07-13 +========== + +* Updated terminology to match the ``frontend-base`` reference implementation: build-time composition of :term:`apps ` into :term:`Sites ` via the :term:`Shell`, in place of runtime module federation. Renamed "Module Architecture" to :term:`Composable Architecture`. +* Retitled from "Create a unified platform repository" to "Create a unified platform library" (and renamed the file accordingly), since ``frontend-base`` is a library. + 2024-09-13 ========== diff --git a/oeps/architectural-decisions/oep-0065/decisions/0002-frontend-app-migrations.rst b/oeps/architectural-decisions/oep-0065/decisions/0002-frontend-app-migrations.rst deleted file mode 100644 index 8e1745690..000000000 --- a/oeps/architectural-decisions/oep-0065/decisions/0002-frontend-app-migrations.rst +++ /dev/null @@ -1,154 +0,0 @@ -.. _Frontend App Migrations: - -Frontend App Migrations -####################### - -Status -****** - -Accepted - -Summary -******* - -This ADR describes a migration path and simplified repository organization for the ``frontend-app-*`` repositories that is compatible with :term:`Module Architecture`: the :term:`Shell` provided by the `frontend-base `_ library, :term:`module federation`, and frontend :term:`projects `. - -Please see :ref:`Create a unified platform repository` for more information on ``frontend-base``. - -Context -******* - -OEP-65 proposes adopting webpack :term:`module federation` for Open edX :term:`micro-frontends ` (MFEs) as a means to enable :term:`runtime module loading` and :term:`shared dependencies` in the :term:`Open edX frontend`. The OEP lays out a series of changes necessary to enable these capabilities, which it refers to as building a "reference implementation" of runtime module loading and shared dependencies. - -This reference implementation is effectively a new underlying architecture for our frontend. This ADR refers to this new architecture as the :term:`module architecture`, as opposed to the historical :term:`micro-frontend architecture` that has existed prior to OEP-65. - -As part of this paradigm shift, our ``frontend-app-*`` repositories (MFEs) will need to be migrated to work with `frontend-base `_. Of particular note, this will require the repositories to: - -* Adopt a new set of build/development CLI helpers -* Use the :term:`shell` to provide the header, footer, and runtime initialization code, amongst other things. -* Organize their code into loosely-coupled top-level components, which are called :term:`application modules `. - -As we adopt ``frontend-base``, the libraries it replaces will undergo their own deprecation processes, which we will need to coordinate with the migration of micro-frontends included in Open edX releases. After that deprecation, the micro-frontend architecture will cease to be supported. - -Decision -******** - -Each of our ``frontend-app-*`` repositories will migrate from being an independent "micro-frontend application" to being a library of modules that can be loaded into a common :term:`Shell`, deployed as a :term:`Site`. These are called :term:`module libraries `. We will document the migration process in detail. At a high level, this will involve the following changes. - -New Deployment Methods -====================== - -The module libraries will be buildable in several different ways. - -* Built as :term:`imported modules ` into an independent Site using the Shell for initialization, the header and footer, configuration, and other foundational services (logging, analytics, i18n, etc.) -* Built as :term:`federated modules ` to be loaded into the Shell at runtime via webpack module federation. -* Built and released as an NPM package for build-time inclusion in a frontend :term:`Project`, perhaps alongside other modules from other libraries. - -Environment Agnostic -==================== - -The :term:`module libraries ` will no longer contain ``.env`` or ``env.config`` files for any specific environment, including `Devstack `_ and `Tutor `_. Config filename patterns will be added to the ``.gitignore`` file. They will continue to support adding a (git ignored) config file into the repository to build or develop it, but we also expect operators to use Projects and check their config files into those project repositories as their primary way of working with the module libraries. - -Please see :ref:`Frontend Projects` for more information on projects. - -Removed Dependencies -==================== - -Application module libraries will cease to use the following libraries in favor of ``frontend-base``: - -* @openedx/frontend-build -* @edx/frontend-plaform -* @openedx/frontend-plugin-framework -* @edx/frontend-component-header -* @edx/frontend-component-footer -* @openedx/frontend-slot-footer -* @edx/brand -* core-js -* regenerator-runtime - -Peer Dependencies -================= - -We expect module libraries to be dependencies of Frontend Projects by default for most operators. Because of this, the following dependencies will become peer dependencies in the module libraries themselves: - -* @openedx/frontend-base -* @openedx/paragon -* react -* react-dom -* react-redux -* react-router -* react-router-dom -* redux - -New CLI Tools -============= - -The ``fedx-scripts`` CLI tools from ``frontend-build`` will be replaced with the ``openedx`` CLI tools from ``frontend-base``. We'll discuss some of them in detail here, as they help illustrate what the library will be able to do: - -* ``dev`` will start a dev server, loading the repository's modules into the shell in a site. -* ``dev:module`` will start a dev server that provides the modules via module federation. -* ``build`` will create a standalone deployable artifact that uses the shell (similar to the micro-frontend architecture) -* ``build:module`` will create a standalone deployable artifact that provides the modules via module federation. -* ``release`` will package the library for distribution on npm. -* ``serve`` will work with ``build`` or ``build:module`` to locally serve the production assets they generated. -* ``pack`` will work with ``release`` to create a ``.tgz`` file suitable for installing in local git checkouts that depend on the library. (this is a development tool) - -The ``dev``, ``dev:module``, ``build``, and ``build:module`` CLI commands will rely on the existence of a :term:`Site Config` file (the replacement for .env/env.config files) which will not be checked into the module library's repository. - -Distributed as NPM Packages -=========================== - -``frontend-app-*`` repositories that are part of Open edX releases will be expected to be published on NPM as a library which exports its modules. These libraries will primarily be consumed by :term:`projects `. - -Consequences -************ - -As the module architecture stabilizes, ``frontend-app-*`` maintainers and developers will be encouraged to migrate their micro-frontends into module libraries, and to adopt the module architecture provided by ``frontend-base``. (There will be a migration guide.) - -For micro-frontends that are migrated to module libraries using the shell, there will be a deployment approach that mimics the micro-frontend architecture, but which will require operators to adopt a new underlying configuration and build process to achieve a similar result. Each ``frontend-app-*`` repository will need a deprecation process for the micro-frontend configuration and build infrastructure. - -Thinking in Modules -=================== - -Our definition of :term:`module` aligns with the `industry standard definition `_. It is also used in the context of `module federation `_. It's a self-contained part of the frontend that represents a specific part of the :term:`Site`, and can be loaded in a variety of ways. We have several sub-types of module: - -* An *application module* represents a well-bounded sub-area of the Open edX frontend at a particular route path. This might be "courseware", "the login page", or "account settings". There are a number of application modules that are *required* for a functioning Open edX frontend Site. -* A "plugin module" represents an optional UI component that is generally added somewhere in an application module, or in the shell. The header and footer, for instance, would be overridden with alternate implementations via plugin modules. New tabs added to the course homepage are also plugin modules. -* *Service modules* which act as implementations of the logging or analytics services. -* *Script modules* which allow attaching arbitrary scripts to the page. - -Our ``frontend-app-*`` repositories go from being "micro-frontend applications" to being a collection of modules centered around a particular domain (learning, authoring, authn, etc.) The question of which modules belong in which repositories, and where the right boundaries are, is beyond the scope of this ADR. - -Unsupported Customizations -========================== - -The :term:`micro-frontend architecture` took an extreme approach to "flexibility", allowing MFEs to diverge from each other in a variety of ways as described in :ref:`OEP-65 `. As a result, in the process of migrating them to the :term:`module architecture`, there could be unforeseen refactoring that may need to happen in some MFEs that don't map into modules well, or which have customizations that aren't supported by the Shell. While we hope to provide enough extensibility mechanisms to reduce the need for forking or hacky customizations, there will be customizations we haven't anticipated, which the community will need to work around or find ways to support. - -Consistent Dependency Versions -============================== - -Addressing our *lack* of dependency version consistency is one of the primary drivers of OEP-65. - -The shell will support specific versions of shared dependencies (such as React, Paragon, or React Router). All applications loaded into the shell's Site will be expected to use (or at least be compatible) with that version. We intend to create lock-step version consistency of shared dependencies across all applications in the platform. We envision each Open edX release supporting a particular major version of each shared dependency. - -References -********** - -* :ref:`OEP-65: Frontend Composability ` -* :ref:`OEP-65 Frontend Glossary ` -* :ref:`ADR-0001: Unified Platform Repository ` -* :ref:`ADR-0003: Frontend Projects ` - -Change History -************** - -2024-08-28 -========== - -* Document created -* `Pull request #626 `_ - -2024-09-13 -========== - -* Updating the language use to match and reference the frontend glossary. diff --git a/oeps/architectural-decisions/oep-0065/decisions/0002-frontend-apps.rst b/oeps/architectural-decisions/oep-0065/decisions/0002-frontend-apps.rst new file mode 100644 index 000000000..bcad0423d --- /dev/null +++ b/oeps/architectural-decisions/oep-0065/decisions/0002-frontend-apps.rst @@ -0,0 +1,108 @@ +.. _Frontend Apps: + +Frontend Apps +############# + +Status +****** + +Accepted + +Summary +******* + +This ADR describes what frontend :term:`apps ` are in the :term:`Composable Architecture`, and how the ``frontend-app-*`` repositories are organized around them. It also summarizes, at a high level, how existing :term:`micro-frontends ` become apps. + +Context +******* + +OEP-65 proposes composing the :term:`Open edX Frontend` from independently developed :term:`apps ` at build time - into one or more :term:`Sites ` built by a shared :term:`Shell` - as a means to enable build-time composability and :term:`shared dependencies `. The OEP lays out a series of changes necessary to enable these capabilities, which it refers to as building a "reference implementation". + +This reference implementation is effectively a new underlying architecture for our frontend. This ADR refers to this new architecture as the :term:`Composable Architecture`, as opposed to the historical :term:`Micro-frontend Architecture` that has existed prior to OEP-65. + +The ``frontend-app-*`` repositories are central to this architecture: rather than being independently deployed applications, they become the home of the :term:`apps ` that a :term:`Site` composes. This ADR defines what an app is and how these repositories are organized, and summarizes how existing micro-frontends migrate to become apps. + +Please see :ref:`Create a unified platform library` for more information on ``frontend-base``. + +Decision +******** + +The fundamental unit of the frontend is the :term:`App`. + +What an App is +============== + +An :term:`App` is a self-contained part of the :term:`Open edX Frontend` that is composed into a :term:`Site` at build time. An app usually represents a well-bounded area of the UI - such as courseware, the login page, or account settings - and typically owns one or more routes. Beyond its routes, an app can contribute: + +* :term:`Widgets ` - components rendered into a :term:`Slot`, whether its own, another app's, or the :term:`Shell`'s. The header and footer, for instance, can be overridden with alternate implementations via widgets, and new tabs on the course home are widgets. +* *Services* - implementations of platform services such as logging or analytics. +* *Scripts* - arbitrary scripts attached to the page. + +Crucially, there is no separate "plugin" concept. What other frameworks would split into "applications" and "plugins" are, in the :term:`Composable Architecture`, all just apps: an app that contributes a top-level area of the UI and an app that only contributes a :term:`Widget` to another app's :term:`Slot` are the same kind of thing. + +App Repositories +================ + +Apps live in :term:`App Repositories `. These repositories: + +* Are always named ``frontend-app-*``, regardless of what their apps do. This is the single naming convention for frontend application code going forward. +* Provide one or more apps. Most provide a single app, but some provide several related apps centered on a domain (learning, authoring, authn, etc.) that share code and dependencies. The question of which apps belong in which repositories, and where the right boundaries are, is beyond the scope of this ADR. + +Because plugins are apps, components previously built as ``frontend-plugin-framework`` plugins become apps, and repositories previously named ``frontend-plugin-*`` should be renamed to ``frontend-app-*``. There is no longer a meaningful distinction between a "plugin repository" and an "app repository". + +Consequences +************ + +Migration +========= + +Existing :term:`micro-frontends ` (and ``frontend-plugin-*`` repositories) will need to migrate to become :term:`App Repositories ` built on `frontend-base `_. Here are a few essential examples of what needs to happen during the conversion: + +* **The shell takes over the foundations.** Apps no longer initialize ``frontend-platform`` or render their own header and footer; the :term:`Shell` provides application initialization, layout, branding, and services. Apps drop the foundational libraries this replaces - ``frontend-build``, ``frontend-platform``, ``frontend-plugin-framework``, ``frontend-component-header``, ``frontend-component-footer``, and ``@edx/brand`` - in favor of ``frontend-base``. +* **Shared dependencies become peer dependencies.** The dependencies the shell provides - React, Paragon, React Router, Redux, and so on - become peer dependencies of the App Repository rather than direct ones, so that a :term:`Site` resolves them to a single shared version at build time. +* **Configuration moves to the Site.** App Repositories no longer carry environment-specific ``.env`` or ``env.config`` files; configuration lives in a :term:`Site Config` (see :ref:`Frontend Sites`). A repository may keep a git-ignored config file for local development. +* **New tooling.** The ``openedx`` CLI from ``frontend-base`` replaces ``fedx-scripts`` from ``frontend-build``, and App Repositories are published to npm so that :term:`Sites ` can consume their apps. + +The complete, step-by-step migration process will be documented in a `Frontend App Migration How To `_ in the ``frontend-base`` repository. + +As we adopt ``frontend-base``, the libraries it replaces will undergo their own deprecation processes (via the `DEPR process `_), coordinated with the migration of the micro-frontends included in Open edX releases. After that deprecation, the :term:`Micro-frontend Architecture` will cease to be supported. + +Unsupported Customizations +========================== + +The :term:`Micro-frontend Architecture` took an extreme approach to "flexibility", allowing MFEs to diverge from each other in a variety of ways as described in :ref:`OEP-65 `. As a result, in the process of migrating them to the :term:`Composable Architecture`, there could be unforeseen refactoring that may need to happen in some MFEs that don't map into apps well, or which have customizations that aren't supported by the Shell. While we hope to provide enough extensibility mechanisms to reduce the need for forking or hacky customizations, there will be customizations we haven't anticipated, which the community will need to work around or find ways to support. + +Consistent Dependency Versions +============================== + +Addressing our *lack* of dependency version consistency is one of the primary drivers of OEP-65. + +The shell will support specific version ranges of shared dependencies (such as React, Paragon, or React Router). All :term:`apps ` composed into the shell's :term:`Site` will be expected to use (or at least be compatible with) that version. We intend to create lock-step version consistency of shared dependencies across all apps in the platform. We envision each Open edX release supporting a particular major version of each shared dependency. + +References +********** + +* :ref:`OEP-65: Frontend Composability ` +* :ref:`OEP-65 Frontend Glossary ` +* :ref:`ADR-0001: Unified Platform Library ` +* :ref:`ADR-0003: Frontend Sites ` +* `Frontend App Migration How To `_ + +Change History +************** + +2024-08-28 +========== + +* Document created +* `Pull request #626 `_ + +2024-09-13 +========== + +* Updating the language use to match and reference the frontend glossary. + +2026-07-13 +========== + +* Rewritten and generalized to match the ``frontend-base`` reference implementation, and retitled from "Frontend App Migrations" to "Frontend Apps". The Decision now defines what an :term:`App` is; the migration is summarized under Consequences, with the details deferred to the migration how-to in ``frontend-base``. diff --git a/oeps/architectural-decisions/oep-0065/decisions/0003-frontend-projects.rst b/oeps/architectural-decisions/oep-0065/decisions/0003-frontend-projects.rst deleted file mode 100644 index c4c2d1b20..000000000 --- a/oeps/architectural-decisions/oep-0065/decisions/0003-frontend-projects.rst +++ /dev/null @@ -1,149 +0,0 @@ -.. _Frontend Projects: - -Frontend Projects -################# - -Status -****** - -Accepted - -Summary -******* - -This ADR describes the architecture of and rationale for frontend :term:`Projects `, a way of building and customizing the :term:`Open edX Frontend`. - -Context -******* - -OEP-65 proposes adopting webpack :term:`module federation` for Open edX :term:`micro-frontends ` (MFEs) as a means to enable :term:`runtime module loading` and :term:`shared dependencies` in the :term:`Open edX frontend`. The OEP lays out a series of changes necessary to enable these capabilities, which it refers to as building a "reference implementation" of runtime module loading and shared dependencies. - -This reference implementation is effectively a new underlying architecture for our frontend. This ADR refers to this new architecture as the "module" architecture, as opposed to the historical "micro-frontend" architecture that has existed prior to OEP-65. - -As part of this re-architecture, we need a way for operators to work with and deploy the :term:`Shell`, which is a wrapper around our existing micro-frontends that will provide application initialization, the header and footer, and the services (logging, analytics, i18n, etc.) provided by ``frontend-base``'s runtime library. With micro-frontends, operators are expected to check out the MFE, add a configuration file (``.env`` or ``env.config.js``) co-located with the code, and run the build/development commands within the repository. - -If we carried over this paradigm to the Shell, that would imply that operators should check out frontend-base, add a configuration file to their checkout, and run the CLI commands within the frontend-base repository. - -In both cases, there are several important developer experience issues with this approach: - -* A developer or operator should not have to check out the source code of a platform or framework in order to work with it. -* Adding a configuration file to the source code could tacitly encourage forking when it isn't necessary, as operators would like somewhere to check in their configuration. -* There's no clear path or best practice for operators to make customizations beyond simple configuration file changes. -* The approach of adding a config file and customizations to the source code is at odds with industry best practices and developer expectations when working with nearly any popular framework, such as Django, React, Next.js, or Rails. - -Finally, our documentation and best practices around how to build and customize the Open edX frontend have always been a bit of an afterthought, and operators have been left to "figure it out" and rely on tools like Tutor to manage the complexity and uncertainty for them. We believe that the platform itself should take more responsibility for providing best in class tools and patterns for working with the code. - -Decision -******** - -We will create a well-supported, built-in configuration and customization framework by adding support for frontend :term:`Projects `. - -A frontend Project is a repository, created and owned by an operator, responsible for containing all of the frontend configuration and customized modules for an operator's various environments. - -There are two primary types of projects: *site projects* and *module projects*. - -Site Projects -============= - -Site projects serve the shell, contain the bulk of the frontend's configuration, and are a place to check in customizations and extensions to the shell and modules built as part of the project. - -A site project uses a :term:`Site Config` file to define the configuration for a :term:`Site`, which is an independently deployed portion of the Open edX frontend which renders a header, footer, and loads one or more Modules. - -A site project consists of: - -* One or more configuration files which account for all of an operator's config and customizations across all their environments. -* A set of build targets expressed as ``"scripts"`` in ``package.json`` which point at ``openedx`` CLI commands from ``frontend-base``. -* (Optional) A ``src`` sub-folder containing the operator's custom modules and extensions. - -A site project can load modules in several different ways (:term:`Importing `, :term:`Federating `, or :term:`Linking `), as defined in its Site Config. The simplest is to ``import`` the module into the Site Config file so that it is bundled with the Site Project. - -.. image:: ../site-project-architecture.png - -To describe the steps in the above image: - -1. A build is started with the ``npm run build`` command, which references a ``scripts`` entry in the project's package.json. -2. That script delegates to the ``openedx`` CLI ``build`` command provided by ``frontend-base``. -3. The ``build`` CLI command runs webpack with the ``build`` webpack config. -4. Webpack uses the :term:`Shell` - in ``frontend-base`` - as it's entry point. -5. The Shell initialization code imports the :term:`Site Config` (i.e., ``site.config.build.tsx``) file from the project. -6. The :term:`Site Config` file imports any :term:`Application Modules ` from libraries it depends on (defined as ``dependencies`` in package.json), along with any other Modules from the ``src`` sub-folder. - -Module Projects -=============== - -Module projects are a place to put customizations and extensions to federated modules, and contain configuration specific to those modules. - -.. image:: ../module-project-architecture.png - -A Module Project uses a ``config`` data structure in ``package.json`` to define what modules it should bundle for :term:`module federation`. This mirrors the ``config`` data structure in the :term:`module libraries ` (``frontend-app-*`` repositories). It also uses a :term:`Module Config` file for additional configuration of those modules beyond that which will be supplied by the site's :term:`Site Config`, i.e., module-specific configuration. - -To describe the steps in the above image: - -1. A build is started with the ``npm run build:module`` command, which references a ``scripts`` entry in the project's ``package.json``. -2. The script delegates to the ``openedx`` CLI ``build:module`` command provided by ``frontend-base``. -3. The ``build:module`` CLI command runs webpack with the ``build:module`` webpack config. -4. The webpack config reads the module federation config from the project's ``package.json``. This will look something like: - - .. code-block:: json - - { - // ... - "config": { - "name": "myModuleProject", - "exposes": { - "./ModuleOne": "./src/module-one/ModuleOne", - "./ModuleTwo": "./src/module-two/ModuleTwo", - "./ModuleThree": "./src/module-three/ModuleThree" - } - }, - // ... - } - -5. Webpack builds :term:`federated modules ` from the ``src`` based on the above configuration in ``package.json``. -6. The modules will be responsible for importing module-specific configuration from a :term:`Module Config` file. -7. The federated modules in ``src`` may be custom modules, or "pass-through" modules that re-export :term:`modules ` from one of the project's dependencies. Note that webpack module federation cannot export modules from dependencies directly; a thin shim module is required (unfortunately). This will function the same for all module sub-types. - -Implicit Projects -================= - -Fundamentally, site and module projects consist of: - -* A (site or module) config file. -* Appropriate build scripts which use ``openedx`` CLI commands. -* Optionally, the source code of modules to bundle into the project (either in-project or as dependencies). -* For module projects, a ``config`` field in package.json with ``name`` and ``exposes`` sub-fields. - -This means that any repository that satisfies these requirements can act as a project. These are :term:`"implicit" projects `. - -Of particular note, ``frontend-app-*`` repositories will satisfy these requirements if we add a git-ignored Site Config or Module Config file to them, and in fact, we anticipate this will be a desirable way to do local development on module libraries. - -Consequences -************ - -The addition of projects creates a first class way of managing the configuration and customization of an Open edX frontend instance without checking out the source of the Open edX Platform frontend itself. - -As we begin to migrate the frontend to the :term:`module architecture`, operators will need to adjust their development, build and deployment processes to use projects. While this will require some effort, we believe that focusing the customization of the Open edX frontend around projects is a clearer, more approachable paradigm that has significant precident in the industry. - -We expect that there will be edge cases that we didn't anticipate in the module architecture and project paradigm, particularly around customization, which may still require operators to fork the source, but we should endeavor to minimize cases where that's necessary. - -References -********** - -* :ref:`OEP-65: Frontend Composability ` -* :ref:`OEP-65 Frontend Glossary ` -* :ref:`ADR-0001: Unified Platform Repository ` -* :ref:`ADR-0002: Frontend App Migrations ` - -Change History -************** - -2024-09-04 -========== - -* Document created -* `Pull request #626 `_ - -2024-09-13 -========== - -* Updating the language use to match and reference the frontend glossary. diff --git a/oeps/architectural-decisions/oep-0065/decisions/0003-frontend-sites.rst b/oeps/architectural-decisions/oep-0065/decisions/0003-frontend-sites.rst new file mode 100644 index 000000000..27e44298d --- /dev/null +++ b/oeps/architectural-decisions/oep-0065/decisions/0003-frontend-sites.rst @@ -0,0 +1,114 @@ +.. _Frontend Sites: + +Frontend Sites +############## + +Status +****** + +Accepted + +Summary +******* + +This ADR describes the architecture of and rationale for frontend :term:`Sites `, a way of building and customizing the :term:`Open edX Frontend`. + +Context +******* + +OEP-65 proposes constructing the :term:`Open edX Frontend` from independently developed :term:`apps ` into one or more :term:`Sites ` as a means to enable composability and :term:`shared dependencies `. The OEP lays out a series of changes necessary to enable these capabilities, which it refers to as building a "reference implementation". + +This reference implementation is effectively a new underlying architecture for our frontend. This ADR refers to this new architecture as the :term:`Composable Architecture`, as opposed to the historical :term:`Micro-frontend Architecture` that has existed prior to OEP-65. + +As part of this re-architecture, we need a way for operators to work with and deploy the :term:`Shell`, which is a wrapper around our existing micro-frontends that will provide application initialization, the header and footer, and the services (logging, analytics, i18n, etc.) provided by ``frontend-base``'s runtime library. With micro-frontends, operators are expected to check out the MFE, add a configuration file (``.env`` or ``env.config.js``) co-located with the code, and run the build/development commands within the repository. + +If we carried over this paradigm to the Shell, that would imply that operators should check out frontend-base, add a configuration file to their checkout, and run the CLI commands within the frontend-base repository. + +In both cases, there are several important developer experience issues with this approach: + +* A developer or operator should not have to check out the source code of a platform or framework in order to work with it. +* Adding a configuration file to the source code could tacitly encourage forking when it isn't necessary, as operators would like somewhere to check in their configuration. +* There's no clear path or best practice for operators to make customizations beyond simple configuration file changes. +* The approach of adding a config file and customizations to the source code is at odds with industry best practices and developer expectations when working with nearly any popular framework, such as Django, React, Next.js, or Rails. + +Finally, our documentation and best practices around how to build and customize the Open edX frontend have always been a bit of an afterthought, and operators have been left to "figure it out" and rely on tools like Tutor to manage the complexity and uncertainty for them. We believe that the platform itself should take more responsibility for providing best in class tools and patterns for working with the code. + +Decision +******** + +We will create a well-supported, built-in configuration and customization framework by adding support for frontend :term:`Sites `. + +A frontend :term:`Site` is a repository, created and owned by an operator, that contains all of the frontend configuration and customized :term:`apps ` for one independently deployable part of their Open edX frontend. An operator will often have just one Site. An operator may have multiple Sites for a given Open edX frontend if they want to deploy it in multiple, independent pieces. + +A Site uses a :term:`Site Config` file to define its configuration. It renders a header and footer and composes one or more :term:`apps ` into a single build. + +A Site consists of: + +* One or more configuration files which account for all of an operator's config and customizations across all their environments. +* A set of build targets expressed as ``"scripts"`` in ``package.json`` which point at ``openedx`` CLI commands from ``frontend-base``. +* (Optional) A ``src`` sub-folder containing the operator's custom :term:`apps ` and extensions. + +The :term:`apps ` composed into a Site are imported into its :term:`Site Config` and bundled with the Site at build time. They may come from the Site's own ``src`` folder or from :term:`App Repositories ` the Site depends on. + +.. image:: ../site-project-architecture.png + +To describe the steps in the above image: + +1. A build is started with the ``npm run build`` command, which references a ``scripts`` entry in the Site's package.json. +2. That script delegates to the ``openedx`` CLI ``build`` command provided by ``frontend-base``. +3. The ``build`` CLI command runs webpack with the ``build`` webpack config. +4. Webpack uses the :term:`Shell` - in ``frontend-base`` - as its entry point. +5. The Shell initialization code imports the :term:`Site Config` (i.e., ``site.config.build.tsx``) file from the Site. +6. The :term:`Site Config` file imports any :term:`apps ` from :term:`App Repositories ` it depends on (defined as ``dependencies`` in package.json), along with any other apps from the ``src`` sub-folder. + +`frontend-template-site `_ is a reference implementation of a Site: operators can copy it as a starting point and customize it to their needs. + +Implicit Sites +============== + +Fundamentally, a Site consists of: + +* A :term:`Site Config` file. +* Appropriate build scripts which use ``openedx`` CLI commands. +* Optionally, the source code of :term:`apps ` to bundle into the Site (either in-repository or as dependencies). + +This means that any repository that satisfies these requirements can act as a Site. These are *implicit* Sites. + +Of particular note, ``frontend-app-*`` repositories will satisfy these requirements if we add :term:`Site Config` files to them. In fact, we anticipate this will be a desirable way to do local development on :term:`App Repositories `. + +Consequences +************ + +The addition of Sites creates a first class way of managing the configuration and customization of an Open edX frontend instance without checking out the source of the Open edX Platform frontend itself. + +As we begin to migrate the frontend to the :term:`Composable Architecture`, operators will need to adjust their development, build and deployment processes to use Sites. While this will require some effort, we believe that focusing the customization of the Open edX frontend around Sites is a clearer, more approachable paradigm that has significant precedent in the industry. + +We expect that there will be edge cases that we didn't anticipate in the Composable Architecture, particularly around customization, which may still require operators to fork the source, but we should endeavor to minimize cases where that's necessary. + +References +********** + +* :ref:`OEP-65: Frontend Composability ` +* :ref:`OEP-65 Frontend Glossary ` +* :ref:`ADR-0001: Unified Platform Library ` +* :ref:`ADR-0002: Frontend Apps ` +* `frontend-template-site `_ - a reference implementation of a Site + +Change History +************** + +2024-09-04 +========== + +* Document created +* `Pull request #626 `_ + +2024-09-13 +========== + +* Updating the language use to match and reference the frontend glossary. + +2026-07-13 +========== + +* Rewritten to match the ``frontend-base`` reference implementation. The "Project" concept (Site Projects and Module Projects) is replaced by :term:`Sites `, and the module federation "Module Project" deployment path is dropped in favor of composing :term:`apps ` into a Site at build time. Renamed the ADR from "Frontend Projects" to "Frontend Sites". diff --git a/oeps/architectural-decisions/oep-0065/decisions/frontend-glossary.rst b/oeps/architectural-decisions/oep-0065/decisions/frontend-glossary.rst index 5d3941773..f3f3dcb25 100644 --- a/oeps/architectural-decisions/oep-0065/decisions/frontend-glossary.rst +++ b/oeps/architectural-decisions/oep-0065/decisions/frontend-glossary.rst @@ -8,144 +8,65 @@ High Level Concepts .. glossary:: - Guest - A Guest is the :term:`Module` being loaded into a :term:`Site` via module federation. + App + A self-contained part of the :term:`Open edX Frontend` that is composed into a :term:`Site`. An app represents a well-bounded area of the UI - such as courseware, the login page, or account settings - and typically owns one or more routes. It declares its own routes, :term:`Slots `, providers, configuration, and the :term:`Widgets `, services, or scripts it contributes. - Host - A Host is the :term:`Site` loading a :term:`Module` via module federation. + Apps are composed into a :term:`Site` at build time by being imported into its :term:`Site Config`. In the historical :term:`Micro-frontend Architecture`, the ``frontend-app-*`` repositories were independently deployed applications ("MFEs"); in the :term:`Composable Architecture` they become :term:`App Repositories `, each providing one or more apps. Components previously built as ``frontend-plugin-framework`` plugins are likewise converted into apps, each contributing its UI to a :term:`Slot` as a :term:`Widget`. - Micro-frontend - Micro-frontend is an industry standard term for small, composable, independently deployed pieces of a frontend. It has a specific and narrower meaning in Open edX's frontend. Open edX's decoupled frontend architecture has been called the "micro-frontend architecture" since 2018 or so, and the ``frontend-app-*`` repositories, specifically, are referred to as "micro-frontends" or "MFEs" for short. They're often called "micro-frontend applications" as well. Some might argue it's a misnomer, as many of our MFEs are quite large. Regardless, MFEs in Open edX refer to our independently deployed, siloed frontends which do not share dependencies, and which may contain one or more distinct parts of the overall frontend. + App Repository + A repository (``frontend-app-*``) that provides one or more :term:`apps ` in the :term:`Composable Architecture`. Most App Repositories provide a single app, but some provide several related apps - for example, ``frontend-app-learning`` might provide the courseware, course outline, progress, and dates apps, which share code and dependencies specific to their domain. This stands in contrast to how the ``frontend-app-*`` repositories were used in the :term:`Micro-frontend Architecture`, where each was a single deployable application. - To support a cohesive vernacular for our post-MFE architecture (the :term:`Module Architecture`), we propose co-opting "application" to refer to a sub-type of modules - :term:`application modules `. Each of the ``frontend-app-*`` repositories is really a collection of related applications co-located in the repository together because they share code and dependencies unique to their domain. For instance, the modules in the "learning" MFE - course outline, courseware, progress page, dates page, etc. - share a significant amount of code, but may be better thought of as a collection of related apps, not as "the learning app". + Micro-frontend + Micro-frontend is an industry standard term for small, composable, independently deployed pieces of a frontend. It has a specific and narrower meaning in Open edX's frontend. Open edX's decoupled frontend architecture has been called the "micro-frontend architecture" since 2018 or so, and the ``frontend-app-*`` repositories, specifically, are referred to as "micro-frontends" or "MFEs" for short. Some might argue it's a misnomer, as many of our MFEs are quite large. Regardless, MFEs in Open edX refer to our independently deployed, siloed frontends which do not share dependencies, and which may contain one or more distinct parts of the overall frontend. - The rest of this glossary reflects the proposed usage of "application". + The :term:`Composable Architecture` supersedes this approach: the ``frontend-app-*`` repositories become :term:`App Repositories ` rather than standalone applications. Micro-frontend Architecture - This is the name for the Open edX frontend architecture based on ``frontend-platform``, ``frontend-build``, and independent ``frontend-app-*`` repositories that represent :term:`micro-frontends `. It has been super-ceded by the :term:`Module Architecture`. - - Module - A piece of code that provides some specific, tightly coupled functionality, and which is loaded into a :term:`Site` via one of several loading mechanisms (:term:`Imported Module`, :term:`Federated Module`, or :term:`Linked Module`) which can happen at buildtime or runtime. - - There are different sub-types of modules, such as :term:`Applications `, :term:`Plugins `, :term:`Services `, or :term:`Scripts