Skip to content

Frontend Integration - Vue 3 + Pilet Loading Example

This guide demonstrates how to build a complete microfrontend application using Vue 3, ES module pilets, and @productifyfw/core.

Overview

The vue3-example showcases a full implementation of:

  • Vue 3 Shell Application - Main app container using Vue 3 Composition API
  • Dynamic ES Module Pilet Loading - Microfrontend modules loaded from the feed service
  • @productifyfw/core Integration - Type-safe access to configuration and pilet loading
  • Productify Proxy Integration - Configuration injection and authentication

Architecture

┌─────────────────────────────────────────────────────┐
│                 Productify Proxy                    │
│  - OAuth/PAT Authentication                         │
│  - Configuration Injection (window.__PRODUCTIFY__)  │
│  - HTML Middleware                                  │
└──────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│              Vue 3 Shell Application                │
│  - Uses @productifyfw/core for pilet loading        │
│  - Fetches pilets from /pilet-feed                  │
│  - Dynamic ES module imports                        │
│  - Renders pilet extensions                         │
└──────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│             ES Module Pilets                        │
│  - Built with Vite as ES modules                    │
│  - All dependencies bundled                         │
│  - Register components via PiletApi                 │
│  - Access Productify config via @productifyfw/core  │
└─────────────────────────────────────────────────────┘

Quick Start

1. Set Up the Example App

bash
cd fe-integrations/packages/vue3-example
pnpm install
pnpm dev

The app will start and be available at the URL configured in your proxy (e.g., http://vue3-example.localhost).

2. Configure the Proxy

Add to your Caddyfile:

caddyfile
http://vue3-example.localhost {
	vars app_id 64e163af-2f6b-4b2c-9a84-ec3c8dfbbe67

	@auth path /auth/*
	@pilet_feed path /pilet-feed/*
	@language_packs path /language-packs/*
	@pilets path /pilets/*

	# OAuth authentication routes
	route @auth {
		productify_before_auth with {vars.app_id}
		authenticate with pocketportal
	}

	# Pilet feed - Public endpoint, no auth required
	route @pilet_feed {
		uri strip_prefix /pilet-feed
		reverse_proxy 172.17.0.1:8080 {
			rewrite /pilet-feed/{vars.app_id}{uri}
		}
	}

	# Language packs - Public endpoint
	route @language_packs {
		reverse_proxy 172.17.0.1:8080
	}

	# Pilet packages - Public endpoint for fetching pilet bundles
	route @pilets {
		reverse_proxy 172.17.0.1:8080
	}

	# Main application routes - OAuth or PAT
	route /* {
		productify_auth with {vars.app_id}
		productify_tenant_selector {vars.app_id}
		authorize with pocketpolicy
		productify_oauth_validation {vars.app_id}
		productify_html_inject {vars.app_id}
		productify with {vars.app_id}
		reverse_proxy localhost:3001  # Vite dev server
	}
}

3. Access the Application

Visit http://vue3-example.localhost and you'll see:

  • User information from OAuth/PAT
  • Application configuration
  • Enabled modules and pilets
  • Dashboard widgets from loaded pilets

Creating Pilets

1. Pilet Structure

See the example-pilet for a complete implementation.

example-pilet/
├── src/
│   ├── index.ts              # Pilet entry point
│   └── components/
│       └── DashboardWidget.vue
├── vite.config.ts            # ES module build config
└── package.json

2. Pilet Entry Point (index.ts)

typescript
// src/index.ts
import type { PiletApi } from "@productifyfw/core";
import { ProductifyClient } from "@productifyfw/core";
import DashboardWidget from "./components/DashboardWidget.vue";

const pfy = new ProductifyClient();

export function setup(api: PiletApi) {
  // Register a dashboard widget
  api.registerExtension("dashboard-widgets", () => ({
    component: DashboardWidget,
    defaults: {
      title: "User Dashboard",
      refreshInterval: 30000,
    },
  }));

  // Register a page route
  api.registerPage("/dashboard", {
    component: DashboardWidget,
  });

  // Conditional registration based on user roles
  if (pfy.getUser()?.username === "admin") {
    api.registerExtension("admin-tools", () => ({
      component: AdminPanel,
    }));
  }
}

3. Vite Configuration

typescript
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
  plugins: [vue()],
  define: {
    "process.env.NODE_ENV": JSON.stringify("production"),
  },
  build: {
    lib: {
      entry: "./src/index.ts",
      name: "ExamplePilet",
      fileName: "index",
      formats: ["es"], // ES module format
    },
  },
});

4. Build the Pilet

bash
cd example-pilet
pnpm install
pnpm build
# Output: dist/index.js (ES module, ~33 KB with all dependencies bundled)

5. Upload to Manager

Configure CLI in ~/.pfy/config.yaml:

yaml
manager_url: http://manager.localhost # Through the Productify proxy
token: pat_<your_token_here>

Upload using the CLI:

bash
pfy pilet upload \
  --project-id <YOUR_PROJECT_ID> \
  --name example-pilet \
  --version 1.0.5 \
  --file dist/index.js

6. Enable in Frontend Settings

Update your application's frontend settings to include the pilet, it is available on the manager ui.

The pilet will be loaded automatically on the next page refresh.

Pilet Feed Service

The application fetches available pilets from:

GET /pilet-feed/:applicationId

Response format:

json
{
  "items": [
    {
      "name": "example-pilet",
      "version": "1.0.5",
      "link": "/pilets/c00833ec-958f-4977-9bd3-274b58bbfdf6/example-pilet/1.0.5"
    },
    {
      "name": "settings-pilet",
      "version": "1.0.5",
      "link": "/pilets/c00833ec-958f-4977-9bd3-274b58bbfdf6/settings-pilet/1.0.5"
    }
  ]
}

The feed automatically filters pilets based on:

  • Database enabled flag (pilet_packages.enabled)
  • Application's enabled modules list (frontend_settings.module_config.enabled_modules)
  • Project association

The pilet loader fetches each pilet's JavaScript file and dynamically imports it as an ES module.

Configuration Injection

The proxy injects configuration before </head>:

html
<script id="productify-config" type="application/json">
  {
    "application_id": "00000000-0000-0000-0000-000000000001",
    "tenant_id": "tenant-123",
    "user": {
      "id": "user-456",
      "name": "John Doe",
      "email": "john@example.com",
      "roles": ["admin", "editor"]
    },
    "settings": {
      "maxItemsPerPage": 25,
      "theme": "dark"
    },
    "modules": {
      "example-pilet": "1.0.0",
      "dashboard": "2.1.0"
    }
  }
</script>
<script>
  window.__PRODUCTIFY__ = JSON.parse(
    document.getElementById("productify-config").textContent
  );
</script>

Development Tips

Local Development Without Proxy

For development, mock the configuration in index.html:

html
<script id="productify-config" type="application/json">
  {
    "application_id": "test-app",
    "user": {
      "name": "Developer",
      "roles": ["admin"]
    }
  }
</script>

Proxy Configuration

Update vite.config.ts to proxy pilet requests:

typescript
export default defineConfig({
  server: {
    proxy: {
      "/pilet-feed": {
        target: "http://localhost:8080",
        changeOrigin: true,
      },
    },
  },
});

Type Safety

All configuration access is type-safe:

typescript
// TypeScript knows this is a number
const maxItems = pfy.getSetting<number>("maxItemsPerPage", {
  defaultValue: 10,
});

// TypeScript knows this is string | null
const theme = pfy.getConfig<string>("theme");

Troubleshooting

Pilets Not Loading

  1. Check browser console for errors
  2. Verify /pilet-feed/:app_id returns pilet list
  3. Ensure modules are enabled in frontend_settings.module_config.enabled_modules
  4. Check pilet_packages.enabled = true in database
  5. Verify pilet JavaScript files are accessible at /pilets/:project_id/:name/:version

Configuration Not Available

  1. Verify window.__PRODUCTIFY__ is defined in browser console
  2. Check proxy is injecting the configuration (view page source, search for productify-config)
  3. Ensure HTML middleware is configured in Caddyfile
  4. Check OAuth session is valid

ES Module Import Errors

  1. Ensure all dependencies are bundled in pilet build (no external in vite.config)
  2. Check process.env.NODE_ENV is defined in vite.config
  3. Verify pilet exports setup function
  4. Check for syntax errors in pilet code

Vue Reactivity Warnings

  1. Ensure window.Vue = { markRaw } is set in main.ts before loading pilets
  2. Verify pilet loader is calling markComponentRaw() on components
  3. Check that you're using reactive() not shallowReactive() for the registry