Skip to content

[WIP] Build process for frontendbase apps npm workspaces flavor (aka: the good one)#284

Closed
holaontiveros wants to merge 34 commits into
overhangio:mainfrom
WGU-Open-edX:frontend-base-c-side
Closed

[WIP] Build process for frontendbase apps npm workspaces flavor (aka: the good one)#284
holaontiveros wants to merge 34 commits into
overhangio:mainfrom
WGU-Open-edX:frontend-base-c-side

Conversation

@holaontiveros

@holaontiveros holaontiveros commented Mar 26, 2026

Copy link
Copy Markdown

Basic details

This is the first step for the build process of the frontend apps, what it does:

  • Adds a new filter (FRONTEND_APPS) that will be used to know which apps should be frontendbase
  • Adds a new filter (FRONTEND_SITES) to know which "sites" are going to be build, by default we have a local one but operators are going to be able to override the default one or add extra ones
  • Adds CORS patches for the configured sites
  • Adds general functions to iterate and get the right information for the new filters
  • Adds a utility iter_paths which returns the paths that should be used in the Caddyfile, taking in account that they could be either from the old MFEs or from a new frontendapp that didn't have MFE before
  • Adds utility is_frontend_app which checks if the app is "enabled" as a frontend app so Caddy does some path magic
  • Caddyfile know checks if the path is for a frontend enabled app so it returns the right folder for it based on config and serves all possible frontend-sites
  • Adds all the basic files for a frontend-site to be build
    • The files have templating so any configured frontendapp is added dynamically to package.json and site.scss
    • templating for right domains in site.config.x.tsx

Pending things

  • Figure the mounting things for sites and frontend apps
  • use right site.config based on being dev or prod (?)
  • Maybe multi-stage sites part (?)
  • General cleanup and better semantic namings
  • entryPoint configs for apps
  • configs for site
  • pluginSlots equivalent
  • patch locations
  • final readme with the details after we have everything else

How to add a frontendapp

In order to mark something as a frontendbase app you need a plugin that looks like:

from tutormfe.hooks import FRONTEND_APPS

@FRONTEND_APPS.add()
def _add_frontend_apps(apps):
    apps["learner-dashboard"] = {
    "appEntryPoints": {
            "packageName": "@openedx/frontend-app-learner-dashboard",
            "packageVersion": "^1.0.0-alpha",
            "styles": ["app.scss"],
            "components": ["learnerDashboardApp"],
        },
    }
    apps["authn"] = {
    "appEntryPoints": {
            "packageName": "@openedx/frontend-app-authn",
            "packageVersion": "^1.0.0-alpha",
            "styles": ["app.scss"],
            "components": ["authnApp"],
        },
    }
    apps["instructor"] = {
    "appEntryPoints": {
            "packageName": "@openedx/frontend-app-instruct",
            "packageVersion": "^1.0.0-alpha",
            "styles": ["app.scss"],
            "components": ["instructApp"],
        },
    } 
    return apps

and if you need some of those to be manually build and installed in the frontend-site:

@FRONTEND_APPS.add()
def _add_frontend_apps(apps):
    apps["learner-dashboard"] = {
        "repository": "https://github.com/arbrandes/frontend-app-learner-dashboard.git",
        "version": "workspaces-dev",
        "appEntryPoints": {
            "packageName": "@openedx/frontend-app-learner-dashboard",
            "packageVersion": "^1.0.0-alpha",
            "styles": ["app.scss"],
            "components": ["learnerDashboardApp"],
        },
    }
    apps["authn"] = {
        "repository": "https://github.com/arbrandes/frontend-app-authn.git",
        "version": "workspaces-dev",
        "appEntryPoints": {
            "packageName": "@openedx/frontend-app-authn",
            "packageVersion": "^1.0.0-alpha",
            "styles": ["app.scss"],
            "components": ["authnApp"],
        },
    }

    return apps

When no repositoryand versionare specified nothing will be added to the packages/ folder for that frontend-app so npm will try to fetch them from the registry

Any app that it's on this list will be considered a fronend-app so when Caddy shows the content for it, it will show the one on frontend-site instead of the normal app.

By default there's going to be a frontend-site that it's build from files in the tutor-mfe plugin (everything under tutormfe/templates/mfe/build/mfe/frontend-site)

if the operator wants to add a new site or substitute the default one it can be done like:

@FRONTEND_SITES.add()
def _add_frontend_base_apps(sites):
    sites['default'] = {
      "repository": "https://github.com/openedx/frontend-site-custom",
      "version": "main"
      "port": "8080"
    }
    return sites

The structure is the same as adding an MFE the only thing to take in account is that if you want to add a new site with a different name than default you also need to specify which frontend apps will live there so Caddy knows that it should serve those files

@FRONTEND_SITES.add()
def _add_frontend_base_apps(sites):
    sites['new-site'] = {
      "repository": "https://github.com/openedx/frontend-site-custom",
      "version": "main"
      "port": "8082"
    }
    return sites

@FRONTEND_APPS.add()
def _add_frontend_apps(apps):
    apps["learner-dashboard"] = {
        "repository": "https://github.com/openedx/frontend-app-learner-dashboard.git",
        "version": "frontend-base",
        "site": "new-site"
    }
    return apps

In case that further customization is required for the a frontensite the full config looks like:

@FRONTEND_SITES.add()
def _add_frontend_base_apps(sites):
    sites['default'] = {
        "repository": "local",
        "port": 8080,
        "siteConfig": {
            "siteId": "tutor-frontend-site",
            "siteName": "Frontend Template Site",
            "accessTokenCookieName": "edx-jwt-cookie-header-payload",
            "redirectRoleId": "org.openedx.frontend.role.dashboard",
            "frontendBaseVersion": "^1.0.0-alpha",
            "paragonVersion": "^23", 
            "externalRoutes": [
                {
                    "role": "org.openedx.frontend.role.profile",
                    "url": ":1995/profile/",
                },
                {
                    "role": "org.openedx.frontend.role.account",
                    "url": ":1997/account/",
                },
                {
                    "role": "org.openedx.frontend.role.logout",
                    "url": ":8000/logout",
                },
            ],
        },
    },

    return sites

Config values reference

value description default
siteId direct equivalent of siteId inside site.config.xx.tsx 'tutor-frontend-site'
siteName direct equivalent of siteName inside site.config.xx.tsx 'Frontend site'
accessTokenCookieName direct equivalent of accessTokenCookieName inside site.config.xx.tsx 'edx-jwt-cookie-header-payload'
redirectRoleId role id to be used to readirect navigation to the default root route when someone access http://apps.local.openedx.io:8080/ (root route) '' the default would fail and throw a 404 so a role needs to always be defined and pointing to the proper place
frontendBaseVersion allows the configuration of a custom version of frontend-base or assign an alias for it 'tutor-frontend-site'
paragonVersion allows the configuration of a custom version of paragon or assign an alias for it 'tutor-frontend-site'
externalRoutes allows the configuration of the external routes to be configured at site.config.xx.tsx []

Front end slots

Given the current architecture of a frontendbase the slots part has been dramatically simplified and made more powerful, so for complex slot situations you can always create an app based on the template-site and just export the slots you require, but if you need a single file plugin / config for a slot then we get the new @FRONTEND_SLOTS

The usage will be similar to the current @PLUGIN_SLOTS but way more simplified, ex:

@FRONTEND_SLOTS.add()
def _add_app_slots(app_slots):
    app_slots.append(
        (
            "org.openedx.frontend.slot.header.primaryLinks.v1",
            """
            {
                slotId: 'org.openedx.frontend.slot.header.primaryLinks.v1',
                id: 'org.openedx.frontend.widget.learnerDashboard.headerLinkDiscover.v999',
                op: WidgetOperationTypes.APPEND,
                element: (
                    <a href="https://github.com/openedx/frontend-base/blob/main/docs/decisions/0009-slot-naming-and-lifecycle.rst"
                    className="nav-link"
                    >
                    Discover the power of slots!</a>
                )
            }
            """,
        )
    )
    return app_slots

This is a widget that it's configured to add be added at the org.openedx.frontend.slot.header.primaryLinks.v1' ID which exists in the header and Adds a new link next to whichever is already there

image

The main difference is that we no longer need the app name, just the slotID (and currently it's not used...) and the slot config, any slot configured here will be configured in a mini-app which just makes the slots available for the build slots.tsx

New patches

Some new patches were introduced to cover previous available functionality that may be used to extend frontend app functionality

name location description
frontend-site-dockerfile-pre-npm-install Dockerfile Runs inmediatly before site npm install
frontend-site-dockerfile-post-npm-install Dockerfile Runs inmediatly after site npm install
frontend-site-dockerfile-pre-npm-build Dockerfile Runs inmediatly before site npm run build
frontend-site-dockerfile-post-npm-build Dockerfile Runs inmediatly after site npm run build
frontend-slots-env-config-buildtime-imports slotsApp.tsx Lives before the definition fo the slotsApp

@arbrandes arbrandes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Great first stab, thank you!

I do have a lot of comments, though. Apologies in advance.

Also, it would be good to have an addition to the README explaining not only how to use this stuff, but how it works.

Comment thread tutormfe/templates/mfe/apps/mfe/Caddyfile Outdated
Comment thread tutormfe/templates/mfe/build/mfe/frontend-site/public/index.html Outdated
Comment thread tutormfe/templates/mfe/build/mfe/frontend-site/src/site.scss Outdated
Comment thread tutormfe/templates/mfe/build/mfe/frontend-site/package.json Outdated
Comment thread tutormfe/templates/mfe/build/mfe/frontend-site/package.json Outdated
Comment thread tutormfe/templates/mfe/build/mfe/Dockerfile
Comment thread tutormfe/templates/mfe/build/mfe/Dockerfile Outdated
Comment thread tutormfe/plugin.py Outdated
Comment thread tutormfe/templates/mfe/build/mfe/frontend-site/src/homeApp.ts Outdated
Comment thread tutormfe/templates/mfe/apps/mfe/Caddyfile Outdated
@holaontiveros
holaontiveros changed the base branch from release to main March 30, 2026 18:47
@brian-smith-tcril

Copy link
Copy Markdown
Contributor

Took a quick look, most things appear to make sense at first glance, the one thing that stood out to me was:

  • What's the purpose of "styles": ["app.scss"] in appEntryPoints?

Looking into the code and some comments from @arbrandes it started to make sense (it's so we can handle what https://github.com/openedx/frontend-template-site/blob/main/src/site.scss does).

So this really becomes more of a frontend-base/frontend-template-site question than a tutor-mfe question: why do site operators using frontend-template-site need to manually add their apps to both site.config and site.scss?

@holaontiveros

Copy link
Copy Markdown
Author

@brian-smith-tcril I'm guessing because we don't import the css at the App level but at the site.config level, and given that the template-site it's a different site.config from the original dev one from the app then we need to have the imports of all the things somwhere...

Now why we do it like that and not somewhere else may be related with a decision on how we are using webpack maybe? @arbrandes

@arbrandes

Copy link
Copy Markdown
Collaborator

why do site operators using frontend-template-site need to manually add their apps to both site.config and site.scss

The simple answer: app SCSS must be imported somewhere by the consuming application (in our case, the site) because apps are not distributed with pre-compiled CSS.

If y'all can think of a better way to handle this, I'm all ears!

@arbrandes

Copy link
Copy Markdown
Collaborator

Ah, turns out we actually documented this in ADR 8, way-back-when. It's a webpack quirk.

@arbrandes

Copy link
Copy Markdown
Collaborator

@holaontiveros, thanks a lot for this! I squashed this PR into a single commit in a new upstream frontend-base branch (and associated PR) we can use for coordination. While I'm closing this, consider it merged! :)

Note, however, that I already made significant changes to the mechanism in order to simplify it and bring it more in line with how tutor (and tutor-mfe) usually do things (more sources of truth inside templates, instead of configuration variables). I also took a first stab at adding dev mount support (for sites only, so far - still working on apps).

@arbrandes arbrandes closed this Apr 3, 2026
@ahmed-arb ahmed-arb moved this from Pending Triage to Won't fix in Tutor project management Apr 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Won't fix

Development

Successfully merging this pull request may close these issues.

5 participants