Skip to main content

Develop a POS Extension in VS Code

This tutorial takes an extension project generated by the Enactor wizard, opens it in VS Code, and develops React components against a local single page POS application which uses the actual POS shell, running on a Vite dev server, with your extension's routes and component overrides merged in.

Why the Single Page Application

Before this, developing a React extension meant building the federation remote and deploying it into a POS host on every change. That workflow has four problems:

  • No hot reload.
  • The code is transpiled, so the debugger shows a, b, c, d instead of your function names.
  • After saving you have to wait for the full build, or the POS serves you no extension code at all.
  • Even a minimal build takes around 7 seconds.

The generated dev/ application removes all four. It is a dev only app and it never ships, and the extension it loads is identical to the one federation exposes.

What you will learn

How to run the local POS Single Page Application from VS Code and develop extension React components against it, with hot reload and real source names in the debugger.

Pre-requisites

Generate the extension project

Create the extension project with the wizard as normal - this tutorial does not repeat it. In short:

  • File > New > Enactor Development > Enactor Application Extension, then Next.
  • Name the project TrainingPOSExtension, choose the platform version, accept the Maven defaults.
  • Add the extension points you need on the Application Extensions page and click Finish.

The full walkthrough, including every wizard page and the generated deployment artefacts, is in Create a POS Extension.

What the wizard generates

The React side of the extension lives under TrainingPOSExtension/src-js/packages/trainingposextension/:

trainingposextension/
├── src/ ← THE EXTENSION (the real deliverable)
│ ├── routes/routes.jsx → exposed as './ExtensionRoutes'
│ ├── componentOverrides/
│ │ ├── componentsMap.jsx → exposed as './ExtensionComponentMaps'
│ │ └── SignOnHeader.jsx
│ ├── components/ExtensionState/…
│ └── assets/css/enactor.scss

├── dev/ ← THE SPA application (dev-only)
│ ├── main.jsx the local "fake POS host"
│ ├── index.html
│ ├── platform-theme.scss
│ ├── federation-shared-bootstrap.js
│ └── .env

├── vite.config.mts ← production federation build
├── vite.config.dev.mts ← local spa build
└── package.json

Two directories, two roles:

  • src/ is the extension. It is the actual extension project and supports module federation.
  • dev/ is a local stand-in for the POS host. It exists so developers can run it with the federated content already merged in, making development much easier.

The dependency arrow points one way only: dev/ imports from src/, never the reverse. Nothing in src/ references dev/, which is why the production build never even reads the dev files.

Open the project in VS Code

File > Open Folder... and select the extension project from the file system.

Once you open the extension project EXPLORER view should be like this.

Extension project in the VS Code Explorer

Open the integrated terminal with Terminal > New Terminal (`Ctrl+Shift+``).

New Terminal

Then you should navigate into the react project inside src-js/packages

cd src-js/packages/trainingposextension/

Navigating to the React package

Then install dependencies from the React package directory:

pnpm install

Start the local POS Single Page Application

Start the Vite dev server against the dev config:

pnpm run dev

The script runs Vite against the dev config vite --config vite.config.dev.mts and the server comes up on port 5173 by default.

Dev server running

Copy the URL the server prints once it's up and running, then paste it into the web browser running the Web POS (you should have a running Web POS to do this step).

Follow this to build the Web POS

The Single Page Application opens on a connection page. Confirm the Device ID, host name, host port and Process Id, then click Submit. These values are similar to what you used when running the web pos in the tutorials above.

SPA connection page

The POS shell then loads, with your extension merged in.

POS sign-on screen in the SPA

How the dev Single Page Application mirrors production

In production your extension is a guest. The POS host application (react-pos-all-app) loads extensionEntry.js over module federation and pulls the two exposed modules into its provider tree.

In dev, dev/main.jsx becomes the host. It builds the Redux store, assembles the POS provider tree, then imports those same two modules directly, rather than over federation:

import extensionRoutes from "@ext/routes/routes"; // = './ExtensionRoutes'
import extensionComponentMaps from "@ext/componentOverrides/componentsMap"; // = './ExtensionComponentMaps'

const routes = Object.assign({}, EnactorRoutes, extensionRoutes);
const componentsMap = Object.assign({}, extensionComponentMaps);

The Single Page Application consumes exactly the same modules federation exposes, only by direct import. What you see in the dev server is what the POS will load.

The two builds stay apart because:

  • Separate config files - vite.config.mts for production, vite.config.dev.mts for the SPA.
  • Different roots, so different module graphs. The dev config sets root: dev/, so its graph starts at dev/index.html -> dev/main.jsx. The production graph starts at the federation exposes entries in src/.
  • No federation plugin runs in the dev config at all.
  • Output isolation. The production build writes dist/, then copy:to:resources places it under META-INF/resources/. The SPA builds nothing. You never run vite build against the dev config.

Develop a React component

With the dev server running, edit a component under src/ and the browser hot-reloads. Because nothing is transpiled for production, breakpoints land on your own function names.

Add a new prompt route

New prompts are registered in src/routes/routes.jsx the module exposed as ./ExtensionRoutes. You can observe how the route against its prompt URL and the SPA picks it up on save.

For writing the prompt component itself, follow the existing tutorials the component code is identical, only the feedback loop changes:

routes.jsx with a prompt URL registered

Debugging

Since the dev server serves untranspiled source, the component tree and the call stack both show real names.

To stop on a line, add a debugger; statement to your component and save.

Adding a debugger statement

Trigger that code in the POS and the browser breaks on it. The Sources panel shows your own file under src/, the Call Stack names the component (ExtensionState), and Scope lists your real variables - not the a, b, c, d of a transpiled build.

Paused in the debugger with real names

Build and deploy the real extension

The Single Page Application is for development only. To produce the artifact the POS actually loads, build the project as normal Run > Run Configurations > Maven Build > MVN Install (TrainingPOSExtension) which runs the production federation build and copies the output into the extension's resources.

Deployment is unchanged: see Deploy POS Extension.

note

The tutorials that follow were written against the older workflow, where you build the extension and deploy it into a POS host to see each change. You do not have to work that way. Their steps still apply exactly as written. Only the way you run and reload the POS differs. So you can follow any of them using the local Single Page Application from this tutorial instead.