Reuse third party libraries
Encapsulation and complete separation among components/applications may lead to bundle same third-party dependencies multiple times. For instance, you might use the same framework to build your reusable components, and then compose your pages with components that belongs to different libraries.
Import maps
To mitigate this issue, micro-lc ships with native support for import maps.
Since at present time neither Firefox nor Safari support import maps, micro-lc embeds the es-module-shims import maps polyfill. Any composable content of micro-lc can leverage the import maps technique utility.
Suppose you want to use two web components form two different libraries, both having a direct dependency from RxJS.
- Library 1
- Library 2
- micro-lc config
import type { MicrolcApi } from '@micro-lc/orchestrator'
import { filter } from 'rxjs'
class MyAwesomeWebComponent extends HTMLElement {
microlcApi?: MicrolcApi
connectedCallback () {
this.microlcApi?.currentApplication$
.pipe(filter(id => id === 'home'))
.subscribe(() => console.log('We are at home!'))
}
}
customElements.define('my-awesome-web-component', MyAwesomeWebComponent)
import type { MicrolcApi } from '@micro-lc/orchestrator'
import { filter } from 'rxjs'
class MyAwesomeWebComponent extends HTMLElement {
microlcApi?: MicrolcApi
connectedCallback () {
this.microlcApi?.currentApplication$
.pipe(filter(id => id !== 'home'))
.subscribe(() => console.log('We are not at home!'))
}
}
customElements.define('my-other-awesome-web-component', MyAwesomeWebComponent)
applications:
home:
integrationMode: compose
route: "/",
config:
sources:
- "https://my-static-server/library-1.js"
- "https://my-static-server/library-2.js"
content:
- tag: "my-awesome-web-component"
- tag: "my-other-awesome-web-component"
If you bundle the dependency in the libraries, RxJS will be downloaded twice when visiting a page using both the components. However, if you bundle a version of the libraries leaving the import as a bare module import (i.e., without including it in the final bundle), you can leverage import maps functionality to download the shared dependency just once.
Excluding modules at compile time while crafting your bundle is an option available to most JavaScript bundlers (e.g., Rollup). When employed, it requires the browser who runs it to properly interpret the import as a bare ES6 module import.
import { filter } from 'rxjs'
Otherwise, the browser will fire an error like:
Error: Unable to resolve specifier 'rxjs'
To learn more about the topic, visit ES Module Shims repository.
Usage
Import maps can be declared in different sections of micro-lc configuration depending on their scope.
If you want them to be accessible by the whole application (i.e., both layout and all mounted micro-frontends), you can
use top-level importmap
configuration key.
Example
- YAML
- JSON
importmap:
imports:
react: https://esm.sh/react@next
react-dom: https://esm.sh/react-dom@next
scopes:
https://esm.sh/react-dom@next:
/client: https://esm.sh/react-dom@next/client
{
"importmap": {
"imports": {
"react": "https://esm.sh/react@next",
"react-dom": "https://esm.sh/react-dom@next"
},
"scopes": {
"https://esm.sh/react-dom@next": {
"/client": "https://esm.sh/react-dom@next/client"
}
}
}
}
On the contrary, if you want to narrow their scope, you need to use key
sources.importmap
when defining the configuration of a
layout, a composable application or a
custom composable error page.
Example
- YAML
- JSON
# 👇 Custom error pages
settings:
4xx:
404:
integrationMode: compose
config:
sources:
uris: https://my-static-server/my-web-component.js
importmap:
imports:
react: https://esm.sh/react@next
react-dom: https://esm.sh/react-dom@next
scopes:
https://esm.sh/react-dom@next:
/client: https://esm.sh/react-dom@next/client
content:
tag: my-web-component
# 👇 Layout
layout:
sources:
uris: https://my-static-server/my-web-component.js
importmap:
imports:
react: https://esm.sh/react@next
react-dom: https://esm.sh/react-dom@next
scopes:
https://esm.sh/react-dom@next:
/client: https://esm.sh/react-dom@next/client
content:
tag: my-web-component
# 👇 Composable applications
applications:
- compose:
integrationMode: compose
route: ./compose
config:
sources:
uris: https://my-static-server/my-web-component.js
importmap:
imports:
react: https://esm.sh/react@next
react-dom: https://esm.sh/react-dom@next
scopes:
https://esm.sh/react-dom@next:
/client: https://esm.sh/react-dom@next/client
content:
tag: my-web-component
{
"settings": {
"4xx": {
"404": {
"integrationMode": "compose",
"config": {
"sources": {
"uris": "https://my-static-server/my-web-component.js"
},
"importmap": {
"imports": {
"react": "https://esm.sh/react@next",
"react-dom": "https://esm.sh/react-dom@next"
},
"scopes": {
"https://esm.sh/react-dom@next": {
"/client": "https://esm.sh/react-dom@next/client"
}
}
},
"content": {
"tag": "my-web-component"
}
}
}
}
},
"layout": {
"sources": {
"uris": "https://my-static-server/my-web-component.js"
},
"importmap": {
"imports": {
"react": "https://esm.sh/react@next",
"react-dom": "https://esm.sh/react-dom@next"
},
"scopes": {
"https://esm.sh/react-dom@next": {
"/client": "https://esm.sh/react-dom@next/client"
}
}
},
"content": {
"tag": "my-web-component"
}
},
"applications": [
{
"compose": {
"integrationMode": "compose",
"route": "./compose",
"config": {
"sources": {
"uris": "https://my-static-server/my-web-component.js"
},
"importmap": {
"imports": {
"react": "https://esm.sh/react@next",
"react-dom": "https://esm.sh/react-dom@next"
},
"scopes": {
"https://esm.sh/react-dom@next": {
"/client": "https://esm.sh/react-dom@next/client"
}
}
},
"content": {
"tag": "my-web-component"
}
}
}
}
]
}
Regardless of where import maps are declared, their configuration is an object of the following shape:
interface ImportMap {
imports?: Record<string, string>
scopes?: Record<string, Record<string, string>>
}
Key imports
allows control over what URLs get fetched by JavaScript import statements and import() expressions. It is
an object mapping modules to URLs from which they can be fetched.
importmap:
imports:
react: https://esm.sh/react@next
It is often the case that you want to use the same import specifier to refer to multiple versions of a single library,
depending on who is importing them. This encapsulates the versions of each dependency in use, and avoids dependency hell.
This use case is supported through key scopes
which allows you to change the meaning of a specifier within a given
scope.
scopes:
https://esm.sh/react-dom@next:
/client: https://esm.sh/react-dom@next/client