@equinor/fusion-framework-vite-plugin-spa
v3.1.7
Published
Vite plugin for SPA development
Readme
title: Fusion Framework Vite SPA Plugin description: > A powerful Vite plugin for building Single Page Applications (SPAs) with the Fusion Framework. It automates HTML template generation, bootstraps authentication and service discovery, and streamlines portal loading and API proxying. Designed for seamless integration with the Fusion Framework CLI, this plugin provides flexible configuration for both standard and advanced SPA scenarios. tags:
- fusion-framework
- cli
- app-development
- portal-development
- dev-server
- authentication
- configuration
- service-discovery
- equinor
- non-production
- documentation keywords:
- fusion-framework
- vite
- spa
- plugin
- development
- non-production
Fusion Framework Vite SPA Plugin
A powerful Vite plugin for building Single Page Applications (SPAs) with the Fusion Framework. It automates HTML template generation, bootstraps authentication and service discovery, and streamlines portal loading and API proxying. Designed for seamless integration with the Fusion Framework CLI, this plugin provides flexible configuration for both standard and advanced SPA scenarios.
[!CAUTION] This plugin is intended for use in non-production environments only.
[!WARNING] This plugin is developed for usage with
@equinor/fusion-framework-cliand this documentation is intended for deeper understanding of the plugin's capabilities and configuration options.NOTE: Just because there are buttons available doesn't mean you need to press them 🐒
The plugin is written in a modular fashion, allowing for easy customization and extension IF the developer has a deep understanding of the Fusion Framework and its internals.
What It Does
The plugin:
- Bootstraps the Fusion Framework - Initializes core modules including MSAL authentication and service discovery
- Renders a Configured Portal - Loads and renders any portal by ID, as long as it exports a render function
- Registers a Service Worker - Enables authenticated API requests by automatically injecting auth tokens
- Configures Development Environment - Works with
plugin-api-serviceto intercept and proxy authenticated requests during development
[!TIP] The plugin will render the configured portal which can be sourced from:
- A local npm package (like
@equinor/fusion-framework-dev-portal, the default used by CLI)- The Fusion Portal Service (using a portal identifier)
- Any custom portal implementation configured in your environment
How the Plugin Works
flowchart
A[Vite HTML request] -->|index.html| B[Plugin serve SPA]
B -->|bootstrap.js| C[Initialize Fusion Framework]
C --> D[Register Service Worker]
C -->|"/portals/{portalId}@${portalTag}"| E[Fetch Portal Manifest]
E -->|manifest.build.config| F[Fetch Portal Configuration]
F -->|manifest.build.entrypoint| G[Import Portal Source]
G -->|"Fusion Instance"| H[Render Portal]
style A fill:#F00,stroke:#333,stroke-width:1px
style H fill:#00F,stroke:#333,stroke-width:1pxFlow Explanation:
- Vite HTML Request: The plugin hooks into the Vite dev-server and intercepts SPA requests.
- Serve SPA: The plugin serves the SPA by returning the
index.htmlfile. - Initialize Fusion Framework:
bootstrap.jsis loaded- configures the framework (e.g. MSAL, service discovery)
- initializes the framework
- registers service worker (connects to fusion framework)
- Fetch Portal Manifest: The framework fetches the manifest describing the portal to load.
- Fetch Portal Configuration: Additional configuration for the portal is retrieved.
- Load Portal Source File: The main entry file for the portal is loaded based on the manifest.
- Render Portal: Renders the portal with the Fusion Framework.
Getting Started
[!WARNING] This plugin is primarily designed to be used with the Fusion Framework CLI. The CLI scaffolds all required configuration and wiring for you.
Standalone usage is advanced: If you use this plugin outside the CLI, you must provide detailed configuration for authentication, service discovery, portal loading, and more. There is no "one-line" quick start for custom setups. See the Basic Configuration and Configuration Options sections below.
[!NOTE] This plugin should cover most use cases for building SPAs with the Fusion Framework, but advanced configurations may require additional setup. If for some reason you are adventurous enough to write your custom implementation of developer utilities, this guide should help you get started.
Configuration Options
The plugin accepts a variety of configuration options to tailor the SPA to your project's needs. These options control authentication flows, service discovery, and routing behaviors.
Basic Configuration
Here's a comprehensive example showing all major configuration options:
fusionSpaPlugin({
generateTemplateEnv: () => ({
// HTML page title
title: 'My App',
// Portal configuration: specify which portal to load
portal: {
id: 'my-portal', // Portal ID to load and render
// Can be:
// 1. A package name (e.g. '@equinor/fusion-framework-dev-portal', default for CLI)
// 2. An ID from the Fusion Portal Service
// 3. Any other configured portal ID
tag: 'latest', // (Optional) Version tag (defaults to 'latest')
proxy: false, // (Optional) Whether to proxy portal requests through /portal-proxy (defaults to false)
},
// Service Discovery configuration
serviceDiscovery: {
url: 'https://my-server.com/service-discovery',
scopes: ['api://my-app/scope'],
},
// MSAL (Microsoft Authentication Library) configuration
msal: {
tenantId: 'my-tenant-id',
clientId: 'my-client-id',
redirectUri: 'https://my-app.com/auth-callback',
requiresAuth: 'true',
},
// Service Worker configuration for API proxying and authentication
serviceWorker: {
resources: [
{
url: '/app-proxy',
rewrite: '/@fusion-api/app',
scopes: ['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/.default'],
},
],
},
})
});Portal Proxy
The portal proxy feature allows you to route portal entry point requests through a /portal-proxy path prefix. When enabled, the plugin will attempt to load portal code from URLs prefixed with /portal-proxy/, which can be useful when working with proxy servers or development environments that need to intercept and route portal requests.
Behavior:
proxy: true→ Portal loads from/portal-proxy/{assetPath}/{templateEntry}(allows proxy interception)proxy: false→ Portal loads from{assetPath}/{templateEntry}(direct loading)
Configuration Options:
proxy: When set totrue, portal entry points will be prefixed with/portal-proxy
When to Use Portal Proxy:
- Development environments where portal assets need to be served through a proxy
- Deployment scenarios requiring portal routing through specific paths
- When working with the API Service Plugin for advanced portal loading
Example:
portal: {
id: 'my-portal',
tag: 'latest',
proxy: true, // Portal will be loaded from /portal-proxy/{assetPath}/{templateEntry}
}See @equinor/fusion-framework-vite-plugin-api-service for advanced API proxying.
Service Discovery
The Service Discovery component provides a dynamic way to discover and connect to backend services. It's a crucial part of the Fusion Framework architecture that helps applications locate and communicate with various microservices.
Configuration Options:
url: The endpoint URL where the service discovery configuration can be fetched fromscopes: Array of OAuth scopes required to authenticate service discovery requests
Benefits:
- Eliminates hardcoded service endpoints in your application
- Enables dynamic service routing based on environment
- Centralizes service endpoint management
MSAL
The Microsoft Authentication Library (MSAL) configuration handles user authentication with Azure Active Directory. This enables single sign-on capabilities and secure access to protected resources.
Configuration Options:
tenantId: The Azure AD tenant ID associated with your organizationclientId: The client/application ID registered in Azure ADredirectUri: The URL where users are redirected after authenticationrequiresAuth(optional): When set to "true", the application will automatically prompt for login on initial load
Service Worker
The Service Worker component provides powerful capabilities for handling network requests, adding authentication, and enabling offline capabilities in your SPA.
How It Works
sequenceDiagram
participant App as Application
participant SW as Service Worker
participant Main as Fusion Framework
participant API as API Server
App->>SW: fetch('/app-proxy/assets/some-app/resource.json')
alt Route matches a registered resource
SW->>Main: Request auth tokens for scopes
Main-->>SW: Return tokens
SW->>SW: Rewrite URL to /@fusion-api/app/assets/some-app/resource.json
SW->>API: fetch with auth headers
API-->>SW: Response
SW-->>App: Return response
else Route does not match any resource
SW-->>App: Let request pass through (no interception)
endThe Service Worker intercepts network requests made by your application and can modify them before they're sent. This is particularly useful for:
- Adding authentication tokens automatically to API calls
- Rewriting request URLs for proxying purposes
- Enabling offline functionality
- Improving performance through caching
Configuration Options
resources: An array of resource configurations the service worker will manageurl: Path pattern to match incoming requests againstrewrite(optional): Path to rewrite the matched URL toscopes(optional): OAuth scopes to use for authenticating this resource
Complete Example
Here's a detailed example of the Service Worker in action:
// Service Worker configuration example
const serviceWorker = {
resources: [
{
url: '/app-proxy',
rewrite: '/@fusion-api/app',
scopes: [
'2bed749c-843b-413d-8b17-e7841869730f/.default',
'8c24cf81-de7a-435b-ab74-e90b1a7bda0a/.default',
],
},
],
};
// Example: Making a request in your application code
fetch('/app-proxy/assets/some-app/resource-path/resource.json');Request Processing Flow
When the above fetch request is made, the following happens:
- The Service Worker intercepts the request matching the
/app-proxypattern - It sends a message to the main thread requesting authentication tokens for the specified scopes
- The main thread generates the necessary authentication tokens
- The Service Worker rewrites the URL from
/app-proxy/assets/some-app/resource-path/resource.jsonto/@fusion-api/app/assets/some-app/resource-path/resource.json - It adds the authentication headers to the request and executes it against the rewritten URL
- The response is returned to the application as if the original URL was called
[!TIP] The
urlpath doesn't need to correspond to an actual endpoint—it's simply a pattern used for matching requests. This allows you to emulate proxy services in production environments without changing your application code.
[!TIP] For enhanced development capabilities, consider using the
@equinor/fusion-framework-vite-plugin-api-serviceplugin. This plugin creates a dynamic proxy service that can handle requests to the/@fusion-api/apppath by intercepting them in the dev-server and routing them based on service discovery configuration.
Telemetry
The Fusion Framework SPA plugin includes built-in telemetry configuration that automatically sets up console logging for development and debugging purposes. The plugin uses the @equinor/fusion-framework-module-telemetry module to provide structured logging with different severity levels.
Telemetry Levels
The telemetry system supports the following severity levels (ordered from lowest to highest):
- Debug (0): Debugging information useful during development
- Information (1): General information about the system's operation
- Warning (2): Indicates a potential issue that is not critical
- Error (3): Represents an error that has occurred, but the system can continue running
- Critical (4): A severe error that may cause the system to stop functioning
Console Logging
By default, the plugin enables console logging for all telemetry events. You can control the minimum log level displayed in the console using the FUSION_SPA_TELEMETRY_CONSOLE_LEVEL environment variable.
// Environment variable configuration
FUSION_SPA_TELEMETRY_CONSOLE_LEVEL=2 // Only show Warning, Error, and Critical eventsWhen set to a valid number, only telemetry items with a level greater than or equal to the specified value will be logged to the console. For example:
FUSION_SPA_TELEMETRY_CONSOLE_LEVEL=0→ Shows all telemetry events (Debug, Information, Warning, Error, Critical)FUSION_SPA_TELEMETRY_CONSOLE_LEVEL=1→ Shows Information, Warning, Error, and Critical eventsFUSION_SPA_TELEMETRY_CONSOLE_LEVEL=2→ Shows Warning, Error, and Critical events
If the environment variable is not set, the default is FUSION_SPA_TELEMETRY_CONSOLE_LEVEL=1 (Information level and above). If the environment variable contains an invalid value, all telemetry events will be logged to the console.
Custom Telemetry Configuration
For advanced telemetry setup (such as Application Insights integration), you can customize the telemetry configuration by providing a custom bootstrap file. See the Providing Custom Bootstrap section for details.
Configuring through .env File
For greater flexibility—especially in CI/CD pipelines and deployment scenarios—the plugin supports configuration through environment variables in a .env file.
How Environment Variables Work
The plugin reads the .env file and overrides any properties defined in the generateTemplateEnv function with the corresponding environment variables. This approach allows you to:
- Maintain different configurations for development, testing, and production
- Keep sensitive information out of your source code
- Override configuration values during deployment pipelines
Naming Convention
Environment variables follow a specific naming pattern:
- All variables are prefixed with
FUSION_SPA_ - Object paths are converted to snake case with underscores
- Arrays and objects are serialized as JSON strings
Example Conversion
Here's how JavaScript configuration objects map to environment variables:
// JavaScript configuration
{
serviceWorker: {
resources: [...],
},
}
// Becomes this environment variable
FUSION_SPA_SERVICE_WORKER_RESOURCES=[...]
// And can be accessed in your code as
import.meta.env.FUSION_SPA_SERVICE_WORKER_RESOURCESComplete .env Example
# Application basics
FUSION_SPA_TITLE=My App
FUSION_SPA_PORTAL_ID=my-portal # Can be a package name, Fusion Portal Service ID, or any configured portal ID
FUSION_SPA_PORTAL_TAG=latest # (Optional) Version tag (defaults to 'latest')
FUSION_SPA_PORTAL_PROXY=false # (Optional) Whether to proxy portal requests through /portal-proxy (defaults to false)
# Service Discovery configuration
FUSION_SPA_SERVICE_DISCOVERY_URL=https://my-server.com/service-discovery
FUSION_SPA_SERVICE_DISCOVERY_SCOPES=[api://my-app/scope]
# MSAL Authentication configuration
FUSION_SPA_MSAL_TENANT_ID=my-tenant-id
FUSION_SPA_MSAL_CLIENT_ID=my-client-id
FUSION_SPA_MSAL_REDIRECT_URI=https://my-app.com/auth-callback
FUSION_SPA_MSAL_REQUIRES_AUTH=true
# Telemetry configuration
FUSION_SPA_TELEMETRY_CONSOLE_LEVEL=2 # Only log Warning, Error, and Critical events to console
# Service Worker configuration (as JSON string)
FUSION_SPA_SERVICE_WORKER_RESOURCES=[{"url":"/app-proxy","rewrite":"/@fusion-api/app","scopes":["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/.default"]}][!TIP] While environment variables are convenient for deployment scenarios, it's generally recommended to use the
generateTemplateEnvfunction during development for better type checking and easier debugging.
[!IMPORTANT] The
.envfile must be placed in the root of your project. Values defined in the.envfile will override any corresponding values from thegenerateTemplateEnvfunction.
Advanced Customization
For advanced scenarios, the plugin provides options to customize both the HTML template and the application bootstrapping process. These customizations should be approached carefully as they may require deeper understanding of the framework.
Providing a Custom Template
You can provide a completely custom HTML template for your application. This gives you full control over the document structure while still leveraging the plugin's environment variable injection.
[!WARNING] Custom templates move you closer to the edge of the framework's capabilities. Proceed with caution as you'll be responsible for ensuring proper structure and bootstrapping.
Example Custom Template
// Define your custom HTML template
const template = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>%MY_CUSTOM_TITLE%</title>
<script type="module" src="./src/my-custom-bootloader"></script>
</head>
<body>
<h1>%MY_CUSTOM_PROPERTY%</h1>
<div id="app"></div>
</body>
</html>
`;
// Configure Vite with your custom template
import { defineConfig } from 'vite';
import { createViteSPAPlugin } from '@equinor/fusion-framework-vite-plugin-spa';
// Custom prefix for environment variables
const templateEnvPrefix = 'MY_CUSTOM_';
export default defineConfig({
// Define environment variables to inject into the template
define: {
[`import.meta.env.${templateEnvPrefix}PROPERTY`]: '"my-custom-value"',
[`import.meta.env.${templateEnvPrefix}TITLE`]: '"My Application"',
},
// Use the custom template with the plugin
plugins: [createViteSPAPlugin({ template, templateEnvPrefix })],
});[!TIP] For more details on how HTML template variable replacement works in Vite, see the Vite documentation.
Providing Custom Bootstrap
For even more control, you can specify a custom bootstrap file that handles the initialization of your application:
fusionSpaPlugin({
generateTemplateEnv: () => {
return {
// Points to your custom bootstrap file
bootstrap: 'src/my-custom-bootloader.ts',
}
}
});Implementing Your Custom Bootloader
When implementing a custom bootloader, you'll need to handle all the initialization logic that would normally be managed by the default bootloader, including service worker registration:
// custom-bootloader.ts
import { registerServiceWorker } from '@equinor/fusion-framework-vite-plugin-spa/html';
import initializeFramework from './my-custom-framework.js';
// Initialize your framework and register the service worker
const app = await initializeFramework();
registerServiceWorker(app);
// Additional custom initialization code
document.addEventListener('DOMContentLoaded', () => {
console.log('Application fully loaded');
});[!WARNING] When using a custom bootloader, the default ServiceWorker registration is bypassed. You must explicitly call
registerServiceWorker()in your custom bootloader to maintain this functionality, as shown in the example above.
Examples
Here are some common implementation examples to help you get started quickly:
Basic SPA Configuration
// vite.config.ts
import { defineConfig } from 'vite';
import { plugin as fusionSpaPlugin } from '@equinor/fusion-framework-vite-plugin-spa';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react(),
fusionSpaPlugin({
generateTemplateEnv: () => ({
title: 'My React App',
portal: { id: 'my-portal' },
serviceDiscovery: {
url: 'https://dev-server.com/service-discovery',
scopes: ['api://my-app/user.read'],
},
msal: {
tenantId: process.env.TENANT_ID,
clientId: process.env.CLIENT_ID,
redirectUri: 'http://localhost:3000/auth-callback',
requiresAuth: 'true',
},
telemetry: {
consoleLevel: 2, // Show Warning, Error, and Critical events
},
}),
}),
],
});Using with API Service Plugin
// vite.config.ts
import { defineConfig } from 'vite';
import { plugin as fusionSpaPlugin } from '@equinor/fusion-framework-vite-plugin-spa';
import { plugin as apiServicePlugin } from '@equinor/fusion-framework-vite-plugin-api-service';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react(),
fusionSpaPlugin({
generateTemplateEnv: () => ({
title: 'API Service Example',
// Portal to load
portal: {
id: 'my-portal',
tag: 'latest',
},
serviceDiscovery: {
url: 'https://dev-server.com/service-discovery',
scopes: ['api://my-app/user.read'],
},
serviceWorker: {
resources: [
{
url: '/api-proxy',
rewrite: '/@fusion-api/service',
scopes: ['api://backend-service/.default'],
},
],
},
}),
}),
// API Service plugin enables portal loading from service discovery
apiServicePlugin(),
],
});See API Service Plugin docs for more details.
Troubleshooting & FAQ
Common Issues
- Authentication Failures
- Ensure your
tenantIdandclientIdvalues are correct - Check that your application is properly registered in Azure AD
- Verify that the required scopes are configured correctly
- Service Worker Not Working
- Ensure the service worker is properly registered in your bootstrap process
- Check browser console for any registration errors
- Verify that the URL patterns in the resources configuration match your fetch calls
- Environment Variables Not Applied
- Confirm your
.envfile is in the project root - Verify the naming convention follows
FUSION_SPA_*with proper snake_casing - Restart your development server after changing environment variables
Known Issues
| Issue | Impact | Description |
| ----- | ------ | ----------- |
| #3266 | Missing bearer token on proxy assets | When loading remote applications that use assets or code-splitting, the service worker may fail to attach the required Bearer token to requests for these resources. This occurs because the service worker rewrites import.url, which can interfere with proper token injection for asset requests. As a result, protected assets may not load correctly in some scenarios.|
Best Practices & FAQ
- Keep secrets out of source code — use environment variables for sensitive values.
- Leverage the API Service Plugin for advanced API proxying and local development.
- When customizing templates or bootloaders, always test service worker registration and authentication flows.
- For local development:
Contributing
Contributions, bug reports, and feature requests are welcome! See CONTRIBUTING.md for guidelines.
