Step 6: Make your parcel
We briefly touched on webcomponents. Let's dive in custom parcel creation.
Almost any JavaScript script can be a parcel, or any HTML page which includes a JavaScript script.
You can think of a parcel as an IIFE (immediately invoked function expression).
(() => {
// application
})()
Every frontend framework enters the page with an IIFE, maybe packed as a UMD (i.e., React) or an ES module (e.g., Vue).
A React application looks like:
var ReactDOM /* = defined here */
var React /* = defined here */
(() => {
const container = document.getElementById('root')
const root = ReactDOM.client.createRoot(container)
root.render(React.createElement('div', {}, 'Hello!'))
})()
To be included in micro-lc the requirement is to be a valid qiankun plugin which in return is a valid single-spa parcel bundled as UMD script.
Your parcel must export 3 methods:
interface Parcel {
async bootstrap(): null
async mount(): null
async unmount(): null
}
Since outside an ES module there's no unique way of knowing exports, UMD is recommended as bundle output. micro-lc via qiankun will look for exports via a proxy window property getter
Let's embed a button in the parcel and create the relevant exports:
(() => {
// 🧱 mount application here
let container = null
// define the 'exports' object
const exports = {}
window['custom-parcel'] = exports
// define the parcel lifecycle
// 👇 bootstrap
const bootstrap = async () => null
// 👇 mount: called when route is activated
const mount = async (props) => {
container = props.container
const div = window.document.createElement('div')
const button = window.document.createElement('button')
Object.assign(
div.style,
{
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
height: '100%',
justifyContent: 'center',
textAlign: 'center',
}
)
button.textContent = 'Click Me!'
div.appendChild(button)
props.container.appendChild(div)
return null
}
// 👇 unmount: called when another route is activated
const unmount = async () => {
if (container !== null) {
container.childNodes.forEach((element) => element.remove())
}
return (container = null)
}
exports.bootstrap = bootstrap
exports.mount = mount
exports.unmount = unmount
})()
Let's use the same data protocol
technique used in step 2
echo "data:text/javascript;base64,$(base64 -w 0 main.js)"
and then we have some base64 that can be fetched by micro-lc. Let's add a new application
{
...,
"applications": {
...,
"custom": {
"integrationMode": "parcel",
"route": "/custom",
"entry": {
"scripts": [
"📄 paste base64 code here"
]
}
}
}
}
and also let's reference the application in the menu
{
"layout": {
"content": {
"tag": "bk-layout",
"properties": {
...,
"menuItems": [
...,
{
"icon": {
"library": "@fortawesome/free-solid-svg-icons",
"selector": "faBox"
},
"id": "custom",
"label": "Custom",
"type": "application"
}
]
}
},
"sources": [
...
]
}
}
Let's make it more complex. Say we have another in-memory router application: if you navigate to the link you'll see that moving using links does not affect the tab url.
In our custom application we would love to draw a layout, using the compose features but then embedding the custom-parcel
and the
in memory router application side-by-side.
Let's change the application
{
...,
"applications": {
...,
"custom": {
"integrationMode": "compose", 👈 new mode
"route": "/custom",
"config": {
"content": {
"tag": "div",
"content": [
{
... 👈 custom-parcel goes here
},
{
... 👈 and in-memory router application goes here
}
]
}
}
}
}
}
We would love to have the mounting factory provided by micro-lc inside this context. The microfrontend-loader
webcomponent is made for this task.
{
...,
"applications": {
...,
"custom": {
"integrationMode": "compose", 👈 new mode
"route": "/custom",
"config": {
"sources": [
"https://cdn.jsdelivr.net/npm/@micro-lc/orchestrator/dist/microfrontend-loader.js" 👈 add resources for 'microfrontend-loader'
],
"content": {
"tag": "div",
"content": [
{
"tag": "microfrontend-loader",
"properties": {
"application": {
"integrationMode": "parcel",
"entry": {
"scripts": [
"📄 paste base64 code here"
]
}
}
}
},
{
"tag": "microfrontend-loader",
"properties": {
"application": {
"integrationMode": "parcel",
"entry": "https://cdn.mia-platform.eu/micro-lc/examples/0.1.3/static/parcels/react-memory-router/index.html"
}
}
}
]
}
}
}
}
}
microfrontend-loader
takes a unique property application
which is exactly the same object of any micro-lc application but
without a route
since in this context it does not make any sense.
Let's fix some style
{
"sources": [...],
"content": {
"tag": "div",
"attributes": {
"style": "display: flex; justify-content: space-around; height: inherit;"
},
"content": [
{
"tag": "microfrontend-loader",
"attributes": {
"style": "width: 25%;"
},
"properties": {
"application": {
"integrationMode": "parcel",
"entry": {
"scripts": [
"📄 paste base64 code here"
]
}
}
}
},
{
"tag": "microfrontend-loader",
"attributes": {
"style": "flex-grow: 1;"
},
"properties": {
"application": {
"integrationMode": "parcel",
"entry": "https://cdn.mia-platform.eu/micro-lc/examples/0.1.3/static/parcels/react-memory-router/index.html"
}
}
}
]
}
}