Skip to main content
Version: 2.x

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).

main.js
(() => {
// 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:

main.js
(() => {
// 🧱 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"
}
}
}
]
}
}