Skip to content

Frontend

Note

The first section covers the experimental v0.2 frontend plugins, while the second half covers the legacy v0.1 frontend plugins.

Frontend plugins are standalone NPM packages that add new widgets and functionality to the tangram web interface. This system is designed for modularity, allowing you to build and share custom UI components.

1. Project Structure

A frontend plugin is a standard TypeScript/Vue project that produces a library build.

my-tangram-frontend-plugin/
├── package.json
├── vite.config.ts
└── src/
    ├── MyWidget.vue
    └── index.ts

2. Plugin Entry Point (index.ts)

The main file specified in your package.json must export an install function. This function is the plugin's entry point and receives the TangramApi object, which provides methods for interacting with the core application.

src/index.ts
import type { TangramApi } from "@open-aviation/tangram/types";
import MyWidget from "./MyWidget.vue";

export function install(api: TangramApi) {
  // use the API to register a new widget component.
  // the first argument is a unique ID for your widget.
  // the second is the Vue component itself.
  api.registerWidget("my-widget", MyWidget);
}

The TangramApi provides two main functions:

  • registerWidget(id: string, component: Component): Makes your component available to the core UI.
  • getVueApp(): App: Provides access to the core Vue application instance for advanced use cases.

3. vite configuration

To simplify the build process, tangram provides a shared Vite plugin. This handles the complex configuration needed to build your plugin as a library and generate a plugin.json manifest file.

vite.config.ts
import { defineConfig } from "vite";
import { tangramPlugin } from "@open-aviation/tangram/vite-plugin";

export default defineConfig({
  plugins: [tangramPlugin()],
});

This standardized build produces a dist-frontend directory containing your compiled JavaScript and the manifest file. tangram uses this manifest to discover and load your plugin.

4. Building and using your plugin

First, build your frontend assets. If you are in the monorepo, pnpm build will handle this.

Next, ensure the generated dist-frontend directory is included in your Python package's wheel. This is typically done in pyproject.toml.

[tool.hatch.build.targets.wheel.force-include]
"dist-frontend" = "my_plugin/dist-frontend"

Finally, install your Python package and enable it in your tangram.toml:

[core]
plugins = ["my_tangram_plugin"]

When tangram serve runs, it will:

  1. Serve the static assets from your plugin's dist-frontend directory.
  2. Include your plugin in the /manifest.json endpoint.
  3. The core web app will fetch the manifest and dynamically load and install your plugin.
sequenceDiagram
    participant P as Plugin Module
    participant B as Browser
    participant S as Tangram Server

    B->>S: GET /manifest.json
    S-->>B: Respond with {"plugins": ["my_plugin"]}
    B->>S: GET /plugins/my_plugin/plugin.json
    S-->>B: Respond with {"main": "index.js"}
    B->>S: GET /plugins/my_plugin/index.js
    S-->>B: Serve plugin's JS entry point
    Note over B, P: Browser executes plugin code
    P->>B: install(tangramApi)
    Note over B: Plugin registers its widgets

Danger

The following guide is the legacy v0.1 plugins system and should NOT be used. See the above.

Frontend plugins are Vue.js components that can be dynamically loaded into the tangram web application. The common use case is to get data from the backend (REST API or Websocket) and display it in a custom way, such as on a map or in a table.

Vue components

The core Vue components are currently located in the src/components/ directory: plugins can be added to this directory (default fallback location) or in a custom directory, then the full path will be specified as an environment variable.

The usual way to import a component in a vue file is to import it like this:

import MyPlugin from "./components/MyPlugin.vue";

then to include a node in the template:

<MyPlugin />

A plugin component will be imported a bit differently, as it is usually not located in the src/components/ directory. Instead, it will be imported dynamically based on the environment variable TANGRAM_WEB_MYPLUGIN_PLUGIN. The environment variable shall be defined in the .env file, and it should point to the full path of the plugin component file. Then the component should be declared in the vite.config.js file, which is responsible for loading the Vue components dynamically:

plugins: [
  // ..., other settings
  dynamicComponentsPlugin({
    envPath: "../.env",
    fallbackDir: "./src/plugins/",
    availablePlugins: [
      "airportSearch",
      "systemInfo",
      "sensorsInfo",
      "cityPair",
      // list all your plugins here
      "myPlugin", // This is your custom plugin
    ],
  }),
];

Then you will be able to include the node in the template part of another component like this:

<template>
  <plugin-myplugin />
</template>

Tip

There is one use case where it is convenient to have a plugin in the default src/components/ directory.

In some cases, you would like to have several possible implementations for a functionality. This can be done in several Vue files, and you can switch the full path to the file in the .env file. If the environment variable is not defined, the plugin will be loaded from the default src/components/ directory.

TANGRAM_WEB_MYPLUGIN_PLUGIN Resulting component path
undefined src/plugins/MyPlugin.vue
/path/to/plugins/MyPlugin1.vue /path/to/plugins/MyPlugin1.vue
/path/to/plugins/MyPlugin2.vue /path/to/plugins/MyPlugin2.vue

Example usage:

Technical details

Registration process

When Tangram initializes, the plugin system:

  1. reads the list of available plugins
  2. checks for environment variable overrides
  3. imports components from the specified paths or fallbacks
  4. registers the components with the Vue application

Hot reloading

The plugin system monitors any change to the environment configuration and:

  • Invalidates cached module definitions
  • Reloads the plugin components
  • Triggers a page refresh to show the updated components

Implementation Details

The dynamic component system is implemented as a Vite plugin (vite-plugin-dynamic-components.js) that:

  1. Creates a virtual module (virtual:plugin-components) at build time
  2. Dynamically generates import statements based on environment configuration
  3. Exports a registerComponents function that Vue uses during initialization
  4. Watches for changes to the environment variables

Key functions in the implementation:

  • createEnvVarsMap(): Collects environment variables with the TANGRAM_WEB_ prefix
  • generateComponentName(): Converts plugin names to kebab-case component names
  • load(): Generates the dynamic import and registration code
  • configureServer(): Sets up watchers for hot reloading

Troubleshooting

If the component does not load or behaves unexpectedly, consider the following:

  • Verify the environment variable path is correct and absolute
  • Check console logs for fallback path messages
  • Check the web console in process-compose for any errors during component loading
  • Ensure the component is correctly registered in vite.config.js

If you see style conflicts or unexpected behavior:

  • Use scoped styles to prevent CSS leaks
  • Be aware of global styles that might affect your component

If the hot reloading does not work as expected:

  • Verify the env file is being watched correctly
  • Check for errors in the console during reload