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
cd fe-integrations/packages/vue3-example
pnpm install
pnpm devThe 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:
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.json2. Pilet Entry Point (index.ts)
// 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
// 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
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:
manager_url: http://manager.localhost # Through the Productify proxy
token: pat_<your_token_here>Upload using the CLI:
pfy pilet upload \
--project-id <YOUR_PROJECT_ID> \
--name example-pilet \
--version 1.0.5 \
--file dist/index.js6. 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/:applicationIdResponse format:
{
"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>:
<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:
<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:
export default defineConfig({
server: {
proxy: {
"/pilet-feed": {
target: "http://localhost:8080",
changeOrigin: true,
},
},
},
});Type Safety
All configuration access is type-safe:
// 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");Related Documentation
Troubleshooting
Pilets Not Loading
- Check browser console for errors
- Verify
/pilet-feed/:app_idreturns pilet list - Ensure modules are enabled in
frontend_settings.module_config.enabled_modules - Check
pilet_packages.enabled = truein database - Verify pilet JavaScript files are accessible at
/pilets/:project_id/:name/:version
Configuration Not Available
- Verify
window.__PRODUCTIFY__is defined in browser console - Check proxy is injecting the configuration (view page source, search for
productify-config) - Ensure HTML middleware is configured in Caddyfile
- Check OAuth session is valid
ES Module Import Errors
- Ensure all dependencies are bundled in pilet build (no
externalin vite.config) - Check
process.env.NODE_ENVis defined in vite.config - Verify pilet exports
setupfunction - Check for syntax errors in pilet code
Vue Reactivity Warnings
- Ensure
window.Vue = { markRaw }is set in main.ts before loading pilets - Verify pilet loader is calling
markComponentRaw()on components - Check that you're using
reactive()notshallowReactive()for the registry