Skip to content

Developing UI components locally

Stephan Pelikan edited this page Oct 10, 2026 · 2 revisions

Writing a form against a running cockpit is slow work. Module federation and hot module replacement do not get along, so every change means building the bundle again and reloading the browser, and the cockpit and a database have to be up for any of it.

The dev shell is the way out. It is a small web application which imports your components the ordinary way, so the dev server can swap them as you type. Behind it stands the dev shell simulator, which answers the questions the cockpit would answer. Neither a cockpit nor a BPMS is running while you work.

The dev shells for React, Angular and Vue are no longer in the cockpit repository. A dev shell has to fit the user interface it stands in for, so each one moves to the repository of that interface. The packages published so far stay on npmjs.com, and their sources are in the Git history of the cockpit repository. The dev shell simulator stays in the cockpit repository.

  1. The webpack switch
    One condition deciding whether the build is a dev shell or a federated module.
  2. React
    The package, the bootstrap call, and what it renders.
  3. Angular
    The same for Angular, and what the cockpit cannot do with it yet.
  4. The dev shell simulator
    Running it, giving it users, and pointing your application at it.
  5. The proxy your dev server needs
    The two paths, without which the shell stays empty.

The webpack switch

One project builds both, and the environment decides which. In development the entry point is the dev shell; for a production build the module federation plugin is added and the public path is set:

process.env.NODE_ENV !== 'production'
  ? { entry: './test/index.tsx' }                  // run the dev shell
  : { output: { publicPath: '/wm/TestModule/' } }  // build the federated module

The production branch does two things more, which are easy to forget: it removes HtmlWebpackPlugin and MiniCssExtractPlugin, because a federated module is not a page of its own, and it adds ModuleFederationPlugin. The whole configuration is on User task forms and status sites, and development/simulator/src/main/webapp-react in the Git history of the cockpit repository is the worked example.

React

The package is @vanillabp/bc-dev-shell-react. Your test/index.tsx hands it the parts your federated module exposes:

import { bootstrapDevShell } from '@vanillabp/bc-dev-shell-react';
import { theme } from '@vanillabp/bc-shared';
import { UserTaskForm } from '../src/UserTaskForm';
import { UserTaskListCell, userTaskListColumns } from '../src/UserTaskList';
import { WorkflowListCell, workflowListColumns } from '../src/WorkflowList';
import { WorkflowPage } from '../src/WorkflowPage';
import { Header } from '../src/Header';

bootstrapDevShell(
    'root',                                              // the HTML element to render into
    { id: 'TestModule', version: '1.0', uri: '' },       // the workflow module you stand for
    theme,                                               // the Grommet theme to render with
    '/official-api/v1',                                  // where the data behind the components comes from
    UserTaskForm,
    userTaskListColumns,
    UserTaskListCell,
    workflowListColumns,
    WorkflowListCell,
    WorkflowPage,
    { 'Header': Header });                               // optional: your other exposed parts

Start the dev server and you get a small application listing your components. Pick a user task and your form renders with a real task behind it, taken from what your application reported to the simulator. The extra parts get a page each, so a header button can be built here too.

The theme is a parameter rather than a default, which is what lets you develop a form against the theme the cockpit it will run in actually uses.

Angular

The Angular dev shell is @vanillabp/bc-dev-shell-angular. It takes fewer parameters, because Angular bootstraps the application itself:

import { appConfig } from '@vanillabp/bc-dev-shell-angular';

bootstrapApplication(AppComponent, appConfig(
    '/official-api/v1',
    UserTaskFormComponent,
    WorkflowPageComponent,
    { 'header': HeaderComponent },
    [ provideHttpClient() ]));

Your components take their data as inputs, @Input() userTask: BcUserTask in the form and @Input() workflow: BcWorkflow on the page, and the shell resolves the object before it renders them. development/simulator/src/main/webapp-angular in the Git history of the cockpit repository is the example.

One thing has to be said plainly. The dev shell works, and the cockpit cannot load the result of an Angular build. The cockpit loads a webpack module federation container, and an Angular build produces something else, custom elements for instance. So an Angular workflow module can be developed against the dev shell and cannot be shown inside the delivered cockpit today. If you are choosing a framework for a module which has to appear in the cockpit, choose React.

There is a Vue dev shell as well, @vanillabp/bc-dev-shell-vue, built on PrimeVue. The same limitation applies to it.

The dev shell simulator

The dev shell renders your components; something has to give them a user task to render. That is the dev shell simulator, a small Spring Boot application which plays the two halves of the cockpit your components touch. It takes the reports your application sends, serves them back to the dev shell, and supplies the users the shell lets you switch between.

It answers the real interfaces rather than pretending to, which is why it is an application and not a mock inside the shell: what it answers has to be what the cockpit answers, or a form works here and not there.

Download the runnable jar of io.vanillabp.businesscockpit:dev-shell-simulator and write the users you want to look at your screens as:

dev-shell-simulator:
  users:
    - id: john
      email: john@doe.com
      first-name: John
      last-name: Doe
      groups: ADMIN, RISK_ASSESSMENT
      attributes:
        office365-uuid: 7bda0fe7-881e-4aa3-b0ee-258cddb71711
    - id: jane
      email: jane@doe.com
      first-name: Jane
      last-name: Doe
      groups: ADMIN
java -jar dev-shell-simulator-*-runnable.jar --spring.config.additional-location=./users.yaml

The groups matter. A form which shows a button to one group and not to another is exactly what the user switch on the shell's main page is for.

Then point your application at it instead of at a cockpit:

vanillabp:
  cockpit:
    rest:
      base-url: http://localhost:8079/bpms/api/v1

Everything the simulator holds lives in H2 in memory, so the drop-downs of the dev shell are empty after every restart of it. Give H2 a file and they survive:

java -Dspring.datasource.url="jdbc:h2:file:./dev-shell-simulator.h2;DB_CLOSE_ON_EXIT=FALSE" \
    -jar dev-shell-simulator-*-runnable.jar \
    --spring.config.additional-location=./users.yaml

The drop-downs then also hold tasks and cases which are gone from your application and from the engine. Delete the file when that starts confusing you.

The proxy your dev server needs

The dev shell asks for two paths, and your dev server has to forward both to the simulator:

module.exports = {
  devServer: {
    proxy: {
      '/official-api': {
        target: 'http://localhost:8079',
        secure: false,
        changeOrigin: true,
      },
      '/dev-shell': {
        target: 'http://localhost:8079',
        secure: false,
        changeOrigin: true,
      },
    },
  },
};

/official-api is where the user tasks and the cases come from. /dev-shell is the user directory, and without it the user switch on the main page stays empty.

Clone this wiki locally