Skip to content

feat: add frontend-base support#285

Merged
arbrandes merged 1 commit into
mainfrom
frontend-base
Apr 24, 2026
Merged

feat: add frontend-base support#285
arbrandes merged 1 commit into
mainfrom
frontend-base

Conversation

@arbrandes
Copy link
Copy Markdown
Collaborator

@arbrandes arbrandes commented Apr 3, 2026

Description

Parent issue: openedx/frontend-base#133

Sandbox: https://frontend-base.openedx.io/

This PR adds support for frontend-base, a unified framework that replaces frontend-build, frontend-platform, frontend-plugin-framework, frontend-component-header, and frontend-component-footer. It enables frontend apps to be loaded as direct plugins within a single shell application, rather than as separate, independently deployed SPAs.

When frontend apps are configured, the plugin builds a frontend-base site that bundles them together into this shell. The site is served by the same mfe container alongside legacy MFEs, with Caddy routing requests appropriately.

Four frontend apps are shipped here. Two new ones, enabled by default: instructor-dashboard and notifications. And two replacements for legacy MFEs: authn, learner-dashboard. These two are disabled by default. (Because we want to enable all new-style apps at the same time once all MFEs are converted, to minimize operator friction.)

Implementation

The build infrastructure uses an npm workspaces monorepo with some usual suspects in the Dockerfile (site-common, site-prod, mfe-dev). Frontend apps not configured to be pulled from NPM (such as when using a git repository or mounted for development) are installed and built as workspace packages. If one matches a legacy MFE, both are still built so that enabling/disabling apps can be done without an image rebuild, but the legacy MFE is effectively disabled for the end user.

Runtime configuration is fetched from the LMS at /api/frontend_site_config/v1/, populated from a new FRONTEND_SITE_CONFIG dictionary in the LMS settings. For backward compatibility, the endpoint also converts existing MFE_CONFIG and MFE_CONFIG_OVERRIDES to frontend-base's SiteConfig structure, at a lower precedence.

In dev mode, mounting a frontend-site directory or any frontend-app-* repository starts a single mfe-dev service for all apps, with hot-reload support. If the appropriate tutor mount adds are run, individual frontend app repos are bind-mounted as npm workspace packages into both the mfe and mfe-dev containers.

Frontend apps (FRONTEND_APPS) are introduced as a new hook alongside the existing MFE_APPS, with corresponding template helpers, LMS settings patches, and a full set of mfe-site-* template patches for customization.

New Tutor configuration variables: MFE_SITE_PORT, MFE_SITE_REPOSITORY, and MFE_SITE_VERSION.

Upstream blockers

Testing

This PR requires an up-to-date deployment of tutor@main (in particular, you'll need openedx/openedx-platform@57914c5 or newer). Make sure your Tutor installation is current before proceeding.

Install this PR's version of tutor-mfe:

pip install "git+https://github.com/overhangio/tutor-mfe.git@frontend-base"

Then save the configuration and rebuild images. Because the mfe and mfe-dev images both need rebuilding, a tutor dev launch is recommended:

tutor config save
tutor dev launch

Baseline: legacy MFEs should still work. With authn and learner-dashboard disabled (the default), everything should behave exactly as before. All previous existing MFEs should load and function normally, including in development mode. There should be no detectable changes in behavior.

Enabling frontend apps. Next, enable the two core frontend apps that ship disabled (instructor-dashboard and notifications are enabled by default, already). Create a Tutor plugin (e.g. "$(tutor plugins printroot)/enable_frontend_apps.py") with the following content:

from tutormfe.hooks import FRONTEND_APPS

@FRONTEND_APPS.add()
def _enable_core_apps(apps):
    apps["authn"]["enabled"] = True
    apps["learner-dashboard"]["enabled"] = True
    return apps

Then enable the plugin and restart (no image rebuild needed):

tutor plugins enable enable_frontend_apps
tutor config save
tutor dev stop && tutor dev start -d

The frontend-base apps should then be accessible in the browser after being redirected from http://local.openedx.io:8000 to http://apps.local.openedx.io:8080. If you're logged in, you should be taken to the dashboard directly at http://apps.local.openedx.io:8080/learner-dashboard/. If not, you should be taken to http://apps.local.openedx.io:8080/authn/login.

Note: While the focus of this PR is on the frontend-base integration infrastructure, not on the functionality of Authn or Learner Dashboard themselves, any bugs encountered in those apps should still be noted.

Development modes. Try mounting individual frontend app repositories and also the whole frontend site for development. Note the behavior difference depending on whether a given app is enabled: when an app is enabled, mounting it provides hot-reload development of that app within the frontend-base site; when it is not enabled, mounting it starts it as a standalone MFE instead (the legacy behavior). See the "Frontend-base site development" section of the README for details.

TODO

  • POC implementation (pending upstream blocker)
  • Use new runtime configuration endpoint (pending the other upstream blocker)
  • Documentation
  • Wait for upstream blockers to land
  • Add package-lock.json (with a sane way to update it later)
  • Testing

LLM usage notice

Built with assistance from Claude.

Comment thread tutormfe/plugin.py Outdated
"authn": {
"npm_package": "@openedx/frontend-app-authn",
"npm_version": "^1.0.0-alpha || 0.0.0-dev",
"enabled": True,
Copy link
Copy Markdown
Collaborator Author

@arbrandes arbrandes Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the consensus in Axim engineering that these should not be enabled by default for Verawood, primarily because users would have to engage with two different slot configuration APIs: one for Authn and Learner Dash, and another for all the other MFEs.

To get some usage data, we should instead:

  • Try to publicize how easy it is to enable these two apps on and test "the future"
  • Since this PR is expected to land, we can try to add some frontend-base testing to the Verawood testing plan, which should be made possible if we provide a sandbox.

@arbrandes
Copy link
Copy Markdown
Collaborator Author

Upstream blockers have landed, so this is now ready for review.

@arbrandes arbrandes marked this pull request as ready for review April 7, 2026 12:17
@arbrandes arbrandes requested review from hinakhadim and regisb April 7, 2026 12:51
{%- endif %}

{% if mfe_data.unmounted|length > 0 or MFE_HOST_EXTRA_FILES %}
{% if mfe_data.unmounted|length > 0 or not site_mounts or MFE_HOST_EXTRA_FILES %}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious about this logic. I think the main thing that is making it seem confusing to me is that site_mounts (plural) implies we might have multiple sites. In the case where multiple sites exist, one is mounted and one isn't, then site_mounts would be truthy.

I see 2 possible solutions here:

  1. Rename get_site_mounts and site_mounts to get_site_mount and site_mount respectively. That would clarify the limitation of "we only support one site."
  2. Support multiple sites. One possible way to do this would be to follow the MFEMountData class pattern for a SiteMountData class.

Copy link
Copy Markdown
Collaborator Author

@arbrandes arbrandes Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

site_mounts (plural) implies we might have multiple sites

Not necessarily, if you take it to mean: "all of the site-related mounts that go into the mfe-dev service". This includes both the site itself, and/or the apps. So the name is fine, I think.

On the suggestions:

  1. Doesn't work, because there are really multiple mounts possible.
  2. That is explicitly a non-goal of this implementation: there is no scenario I can imagine where multiple sites is a desirable tutor-mfe feature. Users can already override the full site config if they want - I can see why somebody would want to do that - but multiple sites negates most (or at least, many) of the benefits of frontend-base.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. So with

        if is_frontend_app_enabled(app_name):
            mounts.append(("mfe", f"frontend-app-{app_name}-src"))
            mounts.append(("mfe-dev", f"frontend-app-{app_name}-src"))

the frontend-app-* mounts are "site mounts."

Seems like a classic "names are hard" problem. When I first saw get_site_mounts I read it as "get mounts for all the sites" instead of "get all the mounts for the one site."

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually looking into what we can do, here. Be back in a bit. :)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I don't have a better idea for the name (what I commented earlier holds), but you did help clarify the implementation:

78618ae

@arbrandes arbrandes force-pushed the frontend-base branch 5 times, most recently from be78f2a to 7c8fbca Compare April 19, 2026 15:58
@arbrandes
Copy link
Copy Markdown
Collaborator Author

@ahmed-arb, I added a package-lock, as well as tooling to make updating it easier. And because Renovate cannot be used (because package.json is a Jinja template), I added a (daily?) Github workflow to do the job.

@arbrandes
Copy link
Copy Markdown
Collaborator Author

This is blocked on openedx/frontend-base#227. Until then, the build fails:

ERROR in ./src/utils.ts:1:30
23.65 TS2305: Module '"@openedx/frontend-base"' has no exported member 'ExternalScriptLoaderClass'.
23.65   > 1 | import { App, ExternalRoute, ExternalScriptLoaderClass, SiteConfig, SlotOperation } from '@openedx/frontend-base';

@arbrandes arbrandes force-pushed the frontend-base branch 2 times, most recently from ad41a93 to 0aea6c7 Compare April 21, 2026 14:35
@arbrandes
Copy link
Copy Markdown
Collaborator Author

We're unblocked: openedx/frontend-base#227 has merged.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this workflow makes sense to me, but I'm slightly concerned that it won't catch build failures with the updated lockfile.

I know in theory things should stay compatible with our pinning strategy, but I've run into issues where dependencies unintentionally release a breaking change as minor (most recently openedx/paragon#4218 / openedx/paragon#4257)

With renovate PRs this gets caught by CI, but I don't see anything in .github/workflows/test.yml or the test target in the Makefile that would catch things like that.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but the problem is that tutor mfe has never run CI against the actual build, probably because it's too expensive. Probably good for a follow-up conversation with the other maintainers.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Or it's just that this already runs as part of the Docker build process somewhere else not visible from here - which I think is the likely answer.)

Comment on lines +113 to +115
{%- if MFE_SITE_REPOSITORY %}
ADD --keep-git-dir=true {{ MFE_SITE_REPOSITORY }}#{{ MFE_SITE_VERSION }} .
{%- else %}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add MFE_SITE_VERSION in if condition as well.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Addressed!

Comment on lines +25 to +29
{%- for app_name, app in iter_frontend_apps() %}
{%- if app.get("npm_package") %}
"{{ app['npm_package'] }}"{{ "," if not loop.last }}
{%- endif %}
{%- endfor %}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might break if loop.last does not have a npm_package. The second-to-last app that had the npm package will have a trailing comma, will result in invalid JSON.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. This loop was not only dumb, it was wrong (it was missing frontend-base itself). Fixed.

@arbrandes arbrandes requested a review from Faraz32123 April 24, 2026 14:33
Copy link
Copy Markdown
Contributor

@Faraz32123 Faraz32123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Introduces a new build path for @openedx/frontend-base apps using npm
workspaces. A default "frontend-site" is added alongside the existing
MFE build, with its own multi-stage Dockerfile, Caddy routing, and
site config files for both dev and production environments.

Co-Authored-By: javier ontiveros <javier.ontiveros@wgu.edu>
Co-Authored-By: Claude <noreply@anthropic.com>
@arbrandes arbrandes merged commit da83503 into main Apr 24, 2026
2 checks passed
@arbrandes arbrandes deleted the frontend-base branch April 24, 2026 15:28
@github-project-automation github-project-automation Bot moved this from In review to Done in Tutor project management Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

7 participants