demf-share-form
v3.1.5
Published
The Share Form parcel displays and manages a share form functionalities. Provides the management to share resources with other users in the Digital Mashup Editor (DME).
Readme
Digital Enabler Share Form parcel
The Share Form parcel displays and manages a share form functionalities. Provides the management to share resources with other users in the Digital Mashup Editor (DME).
Built with Vue 3, Vuetify 3, and Vite. Designed for Single-SPA integration.
demf-share-form
This repository documents how to create and use a Single-SPA Parcel within the same microfrontend architecture used in the DME workspace.
The concrete example is demf-share-form: the share modal content is exposed as a parcel and mounted on demand by another microfrontend, instead of being registered as an <application> in the root-config layout.
Table of Contents
- When to Use a Parcel
- Final Architecture
- Required Prerequisites
- 1. How to Create the Parcel Microfrontend
- 2. How to Prepare the Root Config
- 3. How to Use the Parcel from Another Microfrontend
- 4. Correct Prop Propagation
- 5. Full Step-by-Step Flow
- 6. Local Development
- 7. Troubleshooting
- 8. What NOT to Do
- 9. Useful References
- 10. Final Checklist
Quick Start
Minimal summary for anyone who only needs to use demf-share-form from an existing microfrontend (full details in sections 3 and 4).
1. Import map (locally, in the root-config public/environment/env.js):
localMicrofrontends: {
"demf-share-form": "http://localhost:9901/mf-app.js"
}2. Props received from the root-config in the parent caller MF:
props: ['mfConfig', 'realm', 'palette']3. Dialog host + mount point in the parent caller MF:
<v-dialog v-model="dialogShare" max-width="500px" scrollable>
<v-card>
<v-card-text>
<div ref="parcelMountPoint"></div>
</v-card-text>
</v-card>
</v-dialog>4. Parcel mount / unmount in the parent caller MF:
async mountShareParcel() {
if (this.parcel) return
const el = this.$refs.parcelMountPoint
const [parcelConfig, { mountRootParcel }] = await Promise.all([
window.importShim('demf-share-form'),
window.importShim('single-spa'),
])
this.parcel = mountRootParcel(parcelConfig, {
domElement: el,
realm: this.realm,
palette: this.palette,
root: 'projects', // API segment for the shared resource
resourceType: 'mashup',
resourceData: this.mutableItem,
'demf-share-form-config': this.shareFormConfig,
})
await this.parcel.mountPromise
},
unmountShareParcel() {
if (this.parcel) {
this.parcel.unmount().catch(console.error)
this.parcel = null
}
},Useful analogy: for a parcel, the object passed to mountRootParcel(parcelConfig, { ...props }) plays the same role that props="..." plays for an <application> in the root-config. The mount mechanism changes, but you are still propagating props to the child microfrontend.
Call unmountShareParcel() both when the dialog closes and in the host component beforeUnmount().
When to Use a Parcel
Use a parcel when:
- you want to mount reusable UI inside another MF's DOM
- you want to open an embedded modal, drawer, or widget without adding a dedicated route
- you want to avoid an always-on MF listening to global events
- you want the calling microfrontend to directly control opening, closing, and the UI container
Do not use a parcel when:
- the MF must live as a standalone page or route
- the MF must always be present in the global layout
- you only need event-to-event communication without embedded UI
Final Architecture
Caller MF (e.g. demf-mashups-list)
-> opens a local v-dialog
-> calls importShim('demf-share-form')
-> mountRootParcel(parcelConfig, { domElement, ...props })
demf-share-form
-> bootstrap / mount / unmount single-spa
-> receives realm, palette, mfConfig, resourceData
-> renders only the modal content
root-config
-> exposes import map and remote configurations
-> does NOT register the parcel in microfrontend-layout.html
-> must support props.domElement in the custom single-spa-vue shimRequired Prerequisites
For a parcel to work in this codebase, all of these conditions must be met:
- the parcel MF must export the
bootstrap,mount, andunmountlifecycles - the root-config must resolve the parcel bundle through the import map
- the custom
single-spa-vuein the root-config must useprops.domElementwhen present - the parcel router must not touch the browser URL
- the caller MF must pass
realm,palette, and the parcel configuration
1. How to Create the Parcel Microfrontend
Start from the Vue 3 / Vite template already used by your microfrontends.
1.1 package.json
The package name must match the name resolved by the import map:
{
"name": "demf-share-form"
}1.2 main.js
The parcel must use single-spa-vue and forward props to the root component.
Minimal example:
import { createApp, h } from 'vue'
import App from './App.vue'
import singleSpaVue from 'single-spa-vue'
import router from './router'
import createVuetifyPlugin from './plugins/vuetify.js'
import i18n from './locales/i18n.js'
import pinia from './store'
let singleSpaProps = {}
const vueLifecycles = singleSpaVue({
createApp,
appOptions: {
render() {
return h(App, {
mfConfig: singleSpaProps['demf-share-form-config'] || {},
palette: singleSpaProps.palette,
realm: singleSpaProps.realm,
resourceType: singleSpaProps.resourceType,
resourceData: singleSpaProps.resourceData,
root: singleSpaProps.root,
})
},
},
handleInstance: (app, props) => {
singleSpaProps = props
app.use(createVuetifyPlugin(props.palette || {}, i18n))
app.use(router)
app.use(i18n)
app.use(pinia)
},
})
export const bootstrap = vueLifecycles.bootstrap
export const mount = vueLifecycles.mount
export const unmount = vueLifecycles.unmount1.3 App.vue
The parcel root component must be embedded content, not a full page.
So:
- do not use
v-appas a local root - do not assume its own routes
- do not read context from
window.location - receive everything through props
Recommended props:
props: {
resourceType: String,
resourceData: Object,
root: String,
realm: String,
mfConfig: Object,
}1.4 Parcel Router
If the parcel uses Vue Router, it must use memory history.
Correct:
import { createRouter, createMemoryHistory } from 'vue-router'
export default createRouter({
history: createMemoryHistory(),
routes: [],
})Avoid:
createWebHistory(import.meta.env.BASE_URL)With createWebHistory, the parcel modifies the browser URL and breaks root-config routing.
2. How to Prepare the Root Config
The parcel must not be added to microfrontend-layout.html.
It must instead be resolvable through the import map, and the custom single-spa-vue shim must support mounting on domElement.
2.1 Import map
Add this to the repository that hosts the import map:
{
"imports": {
"demf-share-form": "https://cdn.jsdelivr.net/npm/demf-share-form@latest/mf-app.js"
}
}For local development, in the root-config public/environment/env.js:
localMicrofrontends: {
"demf-share-form": "http://localhost:9901/mf-app.js"
}2.2 Remote config
Add the parcel config file to the tenant config store, for example:
{
"mf": "demf-share-form",
"api": "https://your-api/api"
}Then reference it from the tool main config.json.
2.3 Support for props.domElement in the custom single-spa-vue
Your root-config contains a custom single-spa-vue, so to support parcels the container must be resolved like this:
const container =
props.domElement ||
document.getElementById(props.name) ||
document.querySelector(`#single-spa-application\\:${props.name}`) ||
document.querySelector(`[data-spa-app="${props.name}"]`)This change must be applied in both:
app/public/single-spa-vue-shim.mjsapp/public/systemjs-register.js
If this step is missing, you will get errors like:
Container not found for parcel-13. How to Use the Parcel from Another Microfrontend
The calling MF remains the owner of the v-dialog and the mount point.
3.1 Caller MF props
The caller must receive at least these props from the root-config:
mfConfigrealmpalette
Example:
props: ['mfConfig', 'realm', 'palette']3.2 Dialog host
Create a modal in the caller to host the parcel:
<v-dialog v-model="dialogShare" max-width="500px" scrollable>
<v-card>
<v-toolbar flat color="secondary">
<v-toolbar-title class="text-white">Sharing</v-toolbar-title>
</v-toolbar>
<v-card-text>
<div ref="parcelMountPoint"></div>
</v-card-text>
</v-card>
</v-dialog>3.3 Parcel mount
Full example:
async mountShareParcel() {
if (this.parcel) return
const el = this.$refs.parcelMountPoint
const [parcelConfig, { mountRootParcel }] = await Promise.all([
window.importShim('demf-share-form'),
window.importShim('single-spa'),
])
this.parcel = mountRootParcel(parcelConfig, {
domElement: el,
realm: this.realm,
palette: this.palette,
root: 'projects',
resourceType: 'mashup',
resourceData: this.mutableItem,
'demf-share-form-config': this.shareFormConfig,
})
await this.parcel.mountPromise
}3.4 Parcel unmount
unmountShareParcel() {
if (this.parcel) {
this.parcel.unmount().catch(console.error)
this.parcel = null
}
}Call unmount:
- when you close the dialog
- in the host component
beforeUnmount()
4. Correct Prop Propagation
To behave consistently with the rest of the suite, you must forward these props:
| Prop | Passed by | Purpose |
| --- | --- | --- |
| realm | caller MF | current tenant |
| palette | caller MF | consistent Vuetify theme |
| demf-share-form-config | caller MF | parcel remote config |
| resourceType | caller MF | logical resource type |
| resourceData | caller MF | owner, sharedWith, and already available metadata |
| root | caller MF | API segment, e.g. projects |
If you do not pass palette, the parcel uses Vuetify default colors and the modal appears with a different theme.
5. Full Step-by-Step Flow
- create the parcel MF from the template
- expose
bootstrap,mount, andunmount - make the root component generic and props-driven
- use
createMemoryHistory()in the router - publish or serve
mf-app.js - add the parcel to the import map
- add the parcel remote config
- enable
props.domElementin the root-config customsingle-spa-vue - add
paletteto the props received by the caller - open a
v-dialogin the caller with<div ref="parcelMountPoint"> - execute
mountRootParcel(parcelConfig, { domElement, ...props })in the caller - execute
parcel.unmount()on close
6. Local Development
Inside the parcel app/ folder:
npm install
npm run devThe bundle will be served at:
http://localhost:9901/mf-app.jsThen make sure the root-config env.js points to that URL in localMicrofrontends.
7. Troubleshooting
Error: Container not found for parcel-X
Possible causes:
- the root-config does not use
props.domElementin the customsingle-spa-vue - the caller invokes
mountRootParcelwithoutdomElement - the mount point ref does not exist, or the dialog is not rendered yet
Checks:
- verify
props.domElement || ...in the root-config shim - verify that the caller passes
domElement: this.$refs.parcelMountPoint - verify that the dialog is open before mounting
Error: the browser URL changes to something like http://localhost:9000/http://localhost:9901/...
Cause:
- the parcel router uses
createWebHistory()
Fix:
- use
createMemoryHistory()
Error: the modal colors do not match the rest of the app
Cause:
- the caller does not pass
paletteto the parcel
Fix:
- add
paletteto the caller MF props - pass
:palette="palette"to the host component - pass
palette: this.paletteinmountRootParcel
Error: the parcel opens but does not receive the correct context
Cause:
- the parcel still reads
window.locationor assumes hardcoded data such asmashup
Fix:
- use only
resourceType,resourceData,root,realm, andmfConfig
Error: a parcel menu/dropdown/dialog opens but appears behind the host dialog
Cause:
- each MF (host and parcel) bundles its own copy of Vuetify, so it has an independent z-index stack (
globalStackat module level invuetify/composables/stack.js). If the parcel is mounted inside a host overlay, such as a dialog opened from a menu, the parcel first overlay starts again from the default z-index and can end up below the host dialog.
Fix, depending on the Vuetify component used inside the parcel:
| Component | Direct prop works? | How to set it |
| --- | --- | --- |
| v-dialog, v-menu, v-tooltip, v-overlay | yes | :z-index="N" |
| v-autocomplete, v-select, v-combobox | no (it falls through as an ignored HTML attribute) | :menu-props="{ zIndex: N }" |
Runtime check: inspect document.querySelectorAll('.v-overlay--active') and compare the parcel overlay style attribute (z-index: N) with the host dialog overlay.
Note: if a second overlay from the same bundle opens while the first one is already active, Vuetify still ignores the explicit prop and uses lastZIndex + 10 (useStack) instead. The explicit value only matters for the first overlay opened in the parcel stack.
8. What NOT to Do
- do not register the parcel in
microfrontend-layout.html - do not use
createWebHistory()in the parcel - do not put
v-appinside the parcel root if it is mounted inside another MF - do not assume the parcel has its own route
- do not read owner or id from
window.location - do not forget
paletteif you want to keep the theme consistent
9. Useful References
- Root config:
DME/dme-gui-root - Caller example:
DME/demf-mashups-list - Parcel example:
COMMON/demf-share-form
10. Final Checklist
- import map updated
- parcel remote config present
- custom
single-spa-vuecompatible withprops.domElement - parcel router using
createMemoryHistory() - caller with
props: ['mfConfig', 'realm', 'palette'] mountRootParcel(..., { domElement, realm, palette, resourceData, root, 'demf-share-form-config': ... })parcel.unmount()on close
If all these points are satisfied, the parcel works in the same MF architecture used in the DME workspace.
2. Prepare the dist folder
cp ../README.md dist/README.md cp package.json dist/package.json rm dist/index.html # Linux/macOS
Remove-Item dist/index.html # Windows PowerShell
3. Publish
npm publish --access=public dist
> **NOTE:** Remember to update the `"version"` field in `package.json` before publishing a new release.
---
#### Code Quality
```bash
npm run lint # Lint and fix files with ESLint
npm run format # Format code with PrettierNOTE: Alternatively to the commands indicated above you can use the Vue UI browser interface.
🌐 Internationalization
The demf-share-form microfrontend supports multiple languages through vue-i18n with automatic Vuetify locale integration.
How It Works
- Locale files are stored in
src/locales/*.json(e.g.,en.json,it.json) - The script
scripts/generate-vuetify-locales.mjsautomatically scans these files - Matching Vuetify translations are imported and merged
- The active language is read from
localStorage.getItem('lang')(defaults toen)
Supported Languages
The demf-share-form includes translations for the languages defined in src/locales/:
- English (
en) - Italian (
it) - [Add other languages as needed]
Adding a New Language
- Create a new file:
src/locales/<code>.json(e.g.,es.json) - Add your translations following the existing structure
- Run
npm run devornpm run build - The generator will automatically include Vuetify translations for that language
🎨 Features
- Dynamic theming: Automatically adapts to the palette passed from root-config
- Multi-language support: Full internationalization with vue-i18n
- Responsive design: Works seamlessly on mobile, tablet, and desktop
- Material Design: Built with Vuetify 3 components and Material Design Icons
- Consistent branding: Shows uniform demf-share-form across all Digital Enabler services
- Platform information: Displays version, copyright, and relevant links
📁 Project Structure
demf-share-form/
├── app/
│ ├── src/
│ │ ├── App.vue # Main component
│ │ ├── main.js # Entry point
│ │ ├── components/ # demf-share-form components
│ │ ├── mixins/
│ │ │ └── events.js # Shared setError helper
│ │ ├── locales/
│ │ │ ├── i18n.js # i18n configuration
│ │ │ ├── en.json # English translations
│ │ │ ├── it.json # Italian translations
│ │ │ └── vuetify-generated.js # Auto-generated Vuetify locales
│ │ ├── plugins/
│ │ │ └── vuetify.js # Vuetify configuration
│ │ ├── router/
│ │ └── store/
│ ├── scripts/
│ │ └── generate-vuetify-locales.mjs
│ ├── public/
│ ├── dist/ # Build output
│ ├── package.json
│ ├── vite.config.js
│ └── eslint.config.js
├── docker/
└── README.md🔍 Troubleshooting
demf-share-form not visible
- Verify the import map includes
demf-share-form - Check the layout HTML has the
<application>tag with correct name - Ensure the bundle is accessible at the configured URL
- Look for console errors in the browser developer tools
Configuration not working
- Check that
demf-share-form-configprop is passed in the layout - Verify the configuration JSON structure matches the expected format
- Ensure the root-config is loading the remote configuration correctly
- Check console for warnings about missing configuration
Styling issues
- Ensure the
paletteprop is being passed from root-config - Verify Material Design Icons fonts are loaded
- Check that Vuetify theme configuration is correct
- Clear browser cache and reload
API connection errors
- Verify the
apifield indemf-share-form-config.jsonis correct - Check network tab for failed API requests
- Ensure CORS is properly configured on the backend
- Verify the API endpoint is accessible from the browser
Development server not starting
- Check that port 9001 is not already in use
- Verify Node.js version is compatible (v22+ or v24.8+)
- Try removing
node_modulesand runningnpm installagain - Ensure all dependencies are correctly installed
📚 Tech Stack
Runtime Dependencies
- Vue 3 (^3.5.22) - Progressive JavaScript framework
- Vuetify 3 (^3.10.7) - Material Design component library
- Single-SPA Vue (^3.0.1) - Single-SPA integration for Vue
- Vue Router (^4.6.3) - Official router for Vue.js
- Vue i18n (^9.14.5) - Internationalization plugin
- Vuex (^4.1.0) - State management
- Axios (^1.13.0) - HTTP client
- Material Design Icons (^7.4.47) - Icon library
Development Dependencies
- Vite (^7.1.12) - Next generation frontend tooling
- ESLint (^9.38.0) - Code linting
- Prettier (^3.6.2) - Code formatting
- Vite Plugin Vue DevTools (^8.0.3) - Vue DevTools integration
- Concurrently (^9.2.1) - Run multiple commands
For complete dependencies, see package.json.
📖 Related Documentation
- Digital Enabler Root Config Template
- Digital Enabler Microfrontend Template
- Single-SPA Documentation
- Vue 3 Documentation
- Vuetify 3 Documentation
- Vite Documentation
📄 License
This project is part of Digital Enabler Ecosystem.
© 2025 Engineering Ingegneria Informatica S.p.A.
🆘 Support
For support, questions, or issues:
- Open an issue on GitHub
- Contact the Digital Enabler development team
- Check the Digital Enabler documentation
