Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 250 additions & 20 deletions packages/app-next/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,271 @@

## Overview

This package contains the Red Hat Developer Hub (RHDH) frontend application built using Backstage's new frontend system architecture. The `app-next` package leverages Backstage's new frontend system, which introduces a more modular and extensible architecture compared to the legacy frontend system. This new system is built around the concepts of extensions, plugins, and utility APIs, providing better composability and customization options.
This package is the Red Hat Developer Hub (RHDH) **NFS** (New Frontend System) app shell. It keeps core Backstage plugins that still need to ship with the app, plus a Module Federation feature loader for remotes from `dynamic-plugins-root`.

## Development
Most RHDH product UX loads as **dynamic plugins** (sign-in via **app-auth**, global-header, quickstart, …) so those pieces stay swappable — see [`dynamic-plugins.example.yaml`](./dynamic-plugins.example.yaml).

### Primary Development Workflow
**Homepage exception (temporary):** published OCI homepage overlays are still OFS-shaped for NFS, so this shell statically mounts RHDH `homePageModule` (`@red-hat-developer-hub/backstage-plugin-homepage@1.16.0`) so `/` works for local NFS verification (including homepage cards such as unread notifications). Full DP-first homepage is tracked in [RHIDP-14519](https://redhat.atlassian.net/browse/RHIDP-14519) (promote NFS from `/alpha` to stable). Until then, use the [local export](#local-nfs-export-homepage--theme) path to exercise homepage via Module Federation.

The recommended way to run the new frontend system is using the `backend start:next` command:
## Prerequisites

- Dependencies installed from the repo root: `yarn install`
- A local config overlay such as `app-config.local.yaml` (not committed)
- `skopeo` available on your PATH (for pulling OCI plugin overlays)
- Dynamic plugins installed into `dynamic-plugins-root/` for the UX you want to test

## First-time setup

### 1. Build the app shell

```bash
# from the root of the repo
yarn workspace backend start:next
# from the repo root
EXPERIMENTAL_MODULE_FEDERATION=true yarn workspace app-next build
```

### Standalone Development
### 2. Install default dynamic plugins

You can also run the frontend application independently for development:
Populate `dynamic-plugins-root` from the example plugin set (OCI overlays via `install-dynamic-plugins`). Requires `skopeo`:

```bash
# from the repo root
cp packages/app-next/dynamic-plugins.example.yaml dynamic-plugins.yaml
npx -y @red-hat-developer-hub/cli-module-install-dynamic-plugins@0.2.0 \
install dynamic-plugins-root
```

- Uncomment additional entries in [`dynamic-plugins.example.yaml`](./dynamic-plugins.example.yaml) (sourced from [default.packages.yaml](https://github.com/redhat-developer/rhdh-plugin-export-overlays/blob/main/default.packages.yaml)) before copying if you need more plugins.
- Re-run the install when you change the enabled plugin list.
- Leave homepage/theme OCI entries **commented** — the shell already provides NFS homepage statically; OCI tags would overwrite a local homepage export with OFS assets.

### 3. Local config for homepage

Put NFS `app.extensions` in `app-config.local.yaml` only (not shared `app-config.yaml`):

```yaml
app:
baseUrl: http://localhost:7007
extensions:
- page:home:
config:
path: /
- api:home/visits: true
- app-root-element:home/visit-listener: true
- home-page-layout:home/dynamic-homepage-layout:
config:
customizable: true
- app-root-wrapper:app/global-header: true
- app-root-wrapper:app/drawer: true
- app-drawer-content:quickstart/quickstart: true

backend:
baseUrl: http://localhost:7007
listen:
port: 7007
cors:
origin: http://localhost:7007
methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
credentials: true

auth:
environment: development
providers:
guest: {}

dynamicPlugins:
rootDirectory: dynamic-plugins-root
```

**Sign-in:** load **app-auth** (and typically **app-integrations**) via the example YAML. Configure providers under `auth.providers` in your local overlay.

## Running locally

### Frontend iteration (hot reload)

```bash
# from the root of the repo
yarn workspace app-next start
```

### Other Available Commands
### Full stack with Module Federation

```bash
yarn workspace backend start:next
```

`start:next` sets `APP_CONFIG_app_packageName=app-next` and `ENABLE_STANDARD_MODULE_FEDERATION=true` (see `packages/backend/package.json`). The backend serves `packages/app-next/dist` from its own port (default **7007** unless overridden in `app-config.local.yaml`).

Rebuild the shell after changing files under `packages/app-next/` when using `start:next`:

```bash
EXPERIMENTAL_MODULE_FEDERATION=true yarn workspace app-next build
```

## Verifying the shell

1. With app-auth installed, sign in (guest when `auth.environment: development` and `auth.providers.guest` are set).
2. Open `/` — RHDH homepage layout/widgets should render (`homePlugin` + static `homePageModule`).
3. With global-header / quickstart installed, confirm those surfaces render.
4. Open `/visualizer/tree` and confirm core extensions (for example `page:catalog`, `page:home`) load.

## Testing homepage cards (e.g. unread notifications)

NFS homepage cards are **not** OFS `home.page/cards` mount points. They register as `HomePageWidgetBlueprint` extensions on `page:home`.

The unread-notifications card lives in a local/patched `@backstage/plugin-notifications` as `notificationsHomeModule` (MF expose `notifications-home-module`). Stock npm / RHDH wrapper OCI builds do **not** include that widget yet.

### A. Keep NFS homepage in the shell (default)

No change needed: `App.tsx` already includes `homePlugin` + `homePageModule`.

### B. Export notifications (+ signals) into `dynamic-plugins-root`

From a Backstage checkout that contains the NFS home widget (exposes `.`, `alpha`, and `notifications-home-module`):

```bash
# Frontend — prefer package bundle / export that generates MF assets
cd /path/to/backstage/plugins/notifications
yarn build --role frontend-dynamic-container
# or: npx backstage-cli package bundle --output-destination <rhdh>/dynamic-plugins-root
# Ensure the result lands under:
# <rhdh>/dynamic-plugins-root/backstage-plugin-notifications-dynamic
# (or equivalent) with dist/mf-manifest.json exposing notifications-home-module

# Also export signals (frontend + backend) if not already present — live updates
# depend on them. Use your usual wrapper/export flow into dynamic-plugins-root.
```

Sanity check:

```bash
python3 -c "import json; m=json.load(open('dynamic-plugins-root/backstage-plugin-notifications-dynamic/dist/mf-manifest.json')); print([e['name'] for e in m['exposes']])"
# expect: ['.', 'alpha', 'notifications-home-module']
```

Backend notifications (and signals backend) must also be available under `dynamic-plugins-root` for the API to work.

- `yarn build` - Build the application for production
- `yarn test` - Run the test suite
- `yarn lint` - Run linting checks
- `yarn clean` - Clean build artifacts
### C. Enable the widget in `app-config.local.yaml`

```yaml
app:
extensions:
- home-page-widget:home/unread-notifications: true
# optional layout (widget name = HomePageWidgetBlueprint params.name)
- home-page-layout:home/dynamic-homepage-layout:
config:
customizable: true
widgetLayout:
UnreadNotifications:
priority: 50
breakpoints:
xl: { w: 6, h: 4 }
```

Do **not** rely on OFS `dynamicPlugins.frontend.backstage.plugin-notifications.mountPoints` for `app-next` — that path is legacy Scalprum only.

### D. Rebuild and verify

```bash
EXPERIMENTAL_MODULE_FEDERATION=true yarn workspace app-next build
yarn workspace backend start:next
```

In `/visualizer/tree`, confirm:

- `home-page-widget:home/unread-notifications`
- `api:notifications/notifications` (or equivalent notifications API)
- `page:home`

Then open `/` and confirm the unread notifications card on the grid.

## Local NFS export (homepage & theme)

Use this when you need to iterate on homepage/theme from [rhdh-plugins](https://github.com/redhat-developer/rhdh-plugins) as MF remotes (closer to the long-term DP-first model tracked in [RHIDP-14519](https://redhat.atlassian.net/browse/RHIDP-14519)).

**Homepage (`./alpha`):** `rhdh-plugins` `main` already `export default homePageModule` (see [rhdh-plugins#3794](https://github.com/redhat-developer/rhdh-plugins/pull/3794)), so a local export emits MF expose `alpha`. Published npm **1.16.0** does **not** include that default yet — use `main` (or upcoming **1.16.1+**) for local NFS MF tests.

**Theme:** still needs a temporary default on `./alpha` until theme ships the same pattern:

```ts
// workspaces/theme/plugins/theme/src/alpha/index.ts
export default rhdhThemeModule;
```

Export:

```bash
# from rhdh-plugins (install/build the workspace once if needed)
cd workspaces/homepage/plugins/homepage
yarn build
npx -y @red-hat-developer-hub/cli plugin export --clean --dev \
--dynamic-plugins-root "$HOME/redhat/rhdh/dynamic-plugins-root"

cd ../../../theme/plugins/theme
yarn build
npx -y @red-hat-developer-hub/cli plugin export --clean --dev \
--dynamic-plugins-root "$HOME/redhat/rhdh/dynamic-plugins-root"
```

**Sanity check:**

```bash
python3 -c "import json; m=json.load(open('dynamic-plugins-root/red-hat-developer-hub-backstage-plugin-homepage/dist/mf-manifest.json')); print([e['name'] for e in m['exposes']])"
# expect: ['.', 'alpha'] (alpha requires default export of homePageModule)
```

**When using the local homepage remote:** remove the static `homePageModule` import from `src/App.tsx` (keep `homePlugin`), rebuild app-next, and restart `start:next`. Keep homepage/theme OCI entries commented so install does not overwrite your export.

## Other commands

- `yarn build` — production build (module federation enabled via `package.json` script)
- `yarn test` — run tests
- `yarn lint` — lint checks
- `yarn clean` — remove build artifacts
- `yarn tsc` — typecheck

## Architecture

This application is built using Backstage's [new frontend system](https://backstage.io/docs/frontend-system/architecture/index), which provides:
Static features in `src/App.tsx`:

| Module | Purpose |
|--------|---------|
| `navModule` | Sidebar navigation layout |
| `appVisualizerPlugin` | Extension visualizer (`/visualizer`) |
| `catalogPlugin` / `homePlugin` / `scaffolderPlugin` / `searchPlugin` / `userSettingsPlugin` | Core Backstage plugins (`home` is required host for RHDH homepage + card widgets) |
| `homePageModule` | **Temporary** RHDH homepage layout/widgets — see below |
| `appDrawerModule` | Drawer host for DP content (e.g. quickstart); no app-react overlay yet |
| `rhdhDynamicFrontendFeaturesLoader()` | Temporary helper — loads NFS remotes from `dynamic-plugins-root` |

### Temporary: static `homePageModule`

Published OCI homepage overlays are still **OFS-shaped** (MF expose is only `.`; NFS lives under `/alpha` until [RHIDP-14519](https://redhat.atlassian.net/browse/RHIDP-14519)). The NFS loader skips OFS `.` remotes.

Until an NFS-capable overlay ships (or you use a [local NFS export](#local-nfs-export-homepage--theme)):

1. Keep `@backstage/plugin-home` (`homePlugin`) in the shell — host for `pluginId: 'home'` widgets.
2. Keep `homePageModule` from `@red-hat-developer-hub/backstage-plugin-homepage/alpha` (**1.16.0**) static so `/` works for team verification without a local export.
3. Leave homepage (and theme) OCI entries commented in [`dynamic-plugins.example.yaml`](./dynamic-plugins.example.yaml).

**Removal (after RHIDP-14519 + NFS OCI):** delete the static `homePageModule` import, enable the OCI entry, and rely on `rhdhDynamicFrontendFeaturesLoader`.

### Temporary helper: `rhdhDynamicFrontendFeaturesLoader`

**Location:** [`src/modules/dynamicFeatures/rhdhDynamicFrontendFeaturesLoader.ts`](./src/modules/dynamicFeatures/rhdhDynamicFrontendFeaturesLoader.ts)

**Why it exists:** The stock Backstage [`dynamicFrontendFeaturesLoader`](https://github.com/backstage/backstage/tree/master/packages/frontend-dynamic-feature-loader) only registers each remote module’s **`default`** export. Some RHDH NFS packages still put required shell pieces in **named** exports (for example global-header: default `globalHeaderPlugin` plus named `globalHeaderModule` for the `AppRootWrapper`). Without the helper, those named features never mount.

The helper keeps the same remotes discovery/MF flow, but also registers every loadable named `FrontendPlugin` / `FrontendModule` export (deduped by reference). That also picks up notifications’ `notifications-home-module` expose when present.

**Follow-up / removal criteria:** When published OCI overlays default-export the NFS features that matter (or otherwise make a single `default` sufficient for each remote), switch `App.tsx` back to:

```ts
import { dynamicFrontendFeaturesLoader } from '@backstage/frontend-dynamic-feature-loader';
```

Then delete `src/modules/dynamicFeatures/` (loader + `collectLoadableFeatures` helper and its unit test).

## Related documentation

- **Extension-based architecture**: Modular components that can be composed together
- **Plugin system**: Reusable functionality packages
- **Utility APIs**: Shared services and functionality
- **Route management**: Dynamic routing system for plugin navigation
- **Theming and customization**: Flexible styling and branding options
- [Backstage new frontend system](https://backstage.io/docs/frontend-system/architecture/index)
- [RHIDP-14519](https://redhat.atlassian.net/browse/RHIDP-14519) — promote homepage NFS from `/alpha` to stable
- [app-defaults: app-auth / app-integrations](https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/app-defaults) — intended to load dynamically into RHDH, not statically into this shell
- [rhdh-plugin-export-overlays default.packages.yaml](https://github.com/redhat-developer/rhdh-plugin-export-overlays/blob/main/default.packages.yaml)
Loading
Loading