Skip to main content

Popup

HexaJS popup uses the managed UI pipeline. The popup is a React surface with a pre-wired DI container, so UI code can resolve services and tokens directly.

Enable managed React popup​

In hexa-cli.config.json:

{
"ui": {
"popup": {
"mode": "managed",
"sourceDir": "ui/popup",
"indexFile": "index.html",
"icons": "src/assets/clip-volt-2.svg"
}
}
}

mode: "managed" runs the internal managed React build for popup. sourceDir points to the popup source folder. indexFile is the built HTML entry used in the manifest pipeline. parallelBuild defaults to true and runs managed popup + devtools builds in parallel during standard builds. Set parallelBuild to false to force sequential managed UI builds.

Resolve popup services with DI​

import { inject } from '@hexajs-dev/common';
import { RuntimePort } from '@hexajs-dev/ports';
import { HexaUIClient } from '@hexajs-dev/ui';

const runtimePort = inject(RuntimePort);
const hexaUIClient = inject(HexaUIClient);

Use this pattern in popup React components and hooks when you need runtime APIs or background messaging.

Load state from background​

import { inject } from '@hexajs-dev/common';
import { HexaUIClient } from '@hexajs-dev/ui';
import { configApi } from './api';
import { ConfigResponseMessage, GetConfigMessage } from './messages';

const hexaUIClient = inject(HexaUIClient);
const response = await hexaUIClient.sendMessage<GetConfigMessage, ConfigResponseMessage>(
configApi.Get,
new GetConfigMessage(Date.now())
);

if (response && !hasHexaError(response) && response.config) {
setConfig(response.config);
}

Send partial updates​

import { inject } from '@hexajs-dev/common';
import { HexaUIClient } from '@hexajs-dev/ui';
import { configApi } from './api';
import { ConfigResponseMessage, UpdateConfigMessage } from './messages';

const hexaUIClient = inject(HexaUIClient);

hexaUIClient.sendMessage<UpdateConfigMessage, ConfigResponseMessage>(
configApi.Update,
new UpdateConfigMessage({ theme: 'dark' })
);

Resolve platform token​

import { HEXA_PLATFORM, inject } from '@hexajs-dev/common';

const platform = inject(HEXA_PLATFORM);

For constructor-based DI, token values use @Inject(...):

import { Inject, Injectable, HexaContext, HEXA_PLATFORM } from '@hexajs-dev/common';

@Injectable({ context: HexaContext.UI })
export class PlatformTokenExample {
constructor(@Inject(HEXA_PLATFORM) readonly platform: string) {}
}

Customize the Vite build​

Each managed surface ships with a vite.config.ts in its source directory. HexaJS merges it with the internal build config, so you can extend it without replacing defaults.

ui/popup/vite.config.ts
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
css: {
modules: {
localsConvention: 'camelCase',
},
},
define: {
'import.meta.env.EXTENSION_VERSION': JSON.stringify(process.env.npm_package_version),
},
plugins: [
// add extra Vite plugins here
],
});

Merge behaviour:

  • plugins — your plugins are loaded first, then HexaJS injects its own (React, bootstrap). Duplicates by name are skipped.
  • resolve.alias — merged with path aliases derived from tsconfig.json in the source directory.
  • define — merged with HexaJS platform defines.
  • build — you can extend most options, but managed output controls are preserved: outDir, emptyOutDir, rollupOptions.input, and rollupOptions.output. The internal external list is always preserved and merged with your additions.
note

The React framework plugin (@vitejs/plugin-react) is injected automatically. You do not need to add it — if it is detected in your config it will be skipped to avoid loading it twice.

Notes​

  • Keep business state in background/content stores.
  • Popup should render state returned from messaging calls.
  • Token injection in HexaJS uses inject(TOKEN) or @Inject(TOKEN).
  • Keep docs snippets focused on DI, tokens, and messaging.

Classes​

HexaUIClient​

UI-context HexaClient. Sends messages from popup/devtools UI to the background.

import { HexaUIClient } from '@hexajs-dev/ui';
class HexaUIClient { ... }

Methods​

sendMessage()

Send a message and await a response. Content → background uses runtime.sendMessage. Background → content requires a tabId — use BackgroundHexaClient.sendToTab().

sendMessage<TPayload, TResponse>(target: `${namespace}:${api}`, payload?: TPayload): Promise<TResponse>