https://github.com/marko-js/micro-frame
A Marko tag for building SSR friendly micro frontends.
https://github.com/marko-js/micro-frame
Last synced: 4 months ago
JSON representation
A Marko tag for building SSR friendly micro frontends.
- Host: GitHub
- URL: https://github.com/marko-js/micro-frame
- Owner: marko-js
- License: mit
- Created: 2021-10-05T19:46:59.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-12-10T16:31:32.000Z (6 months ago)
- Last Synced: 2024-12-28T19:02:49.910Z (6 months ago)
- Language: HTML
- Size: 838 KB
- Stars: 55
- Watchers: 9
- Forks: 8
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Code of conduct: .github/CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
@micro-frame/marko
A Marko tag for building SSR friendly micro frontends.# Installation
```console
npm install @micro-frame/marko
```# How it works
This package exposes a `` Marko component that in many ways is similar to a traditional ``.
However, unlike an `iframe`, the content from the `src` is loaded, with streaming support, _directly_ into the existing document.## On the server
When this component is rendered server side, it will make a request to load the embedded html resource. The response is then streamed along side the content for the host page.
Internally [make-fetch-happen](https://github.com/zkat/make-fetch-happen) is used to perform the requests from the server. These means you can also leverage [HTTP Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
## In the browser
When rendered client side a normal fetch request is made to load the embedded html resource. The content of the response will be rendered within the page _as if_ it was a server side render. This includes full streaming support.
Any time the `src` attribute is changed, a new request will be made to load updated html content.
## Why
This module allows for embedded micro frontends with the following benefits:
1. Can take full advantage of streaming, if loaded server side _or_ in the browser.
2. Both the host, and embedded applications simply respond with HTML.
3. Framework agnostic, the child can respond with HTML generated by any tool/framework.Specifically in comparison to iframes it offers the following advantages:
- Usability
- Control over loading & error state rendering.
- Does not break navigation / back button.
- Does not appear differently to screen readers.
- Does not cause issues using native browser API’s that are sometimes restricted in iframes.
- Content can rendered with the rest of the page
- No resizing issues.
- Flows with page content / layout.
- Can escape it’s container, eg for modals
- Performance
- Shares single connection with host (no round trip once iframe makes it to the browser).
- Does not impact SEO (sometimes iframes are not indexed by search engines).
- iframes receive lower priority than other assets on the page, this does not.
- Avoids additional window / browser context (less memory used).
- Avoids boilerplate html, just send fragments (no ``, ``, etc).
- Caches in both the client and host server.## Why not
This module works best when you have applications that are independently developed, potentially with different technology stacks, that you want to glue together.
- Applications broken up this way in general are harder to optimize, deploy, etc.
- Embedded apps _should_ be served from the same origin/TLD to prevent CORS issues. You should not embed _untrusted_ applications, you should consider the embedded application a part of the host page.
- There will always be overhead in this approach, or really any naive micro-frontend setup. This module does not dictate how assets are loaded or shared across applications. If necessary that must be orchestrated between the applications separately. Solutions like [Module federation](https://webpack.js.org/concepts/module-federation/), native ES modules & globally available modules should work fine with `micro-frame`.# Example
```marko
<@loading>
We're still loading...
@loading>
<@catch|err|>
Uh-oh! ${err.message}
@catch>```
# API
## `src`
A (required) path to the embedded html application. This is resolved from the [`origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin) of the of the host application.
```marko
```
With the above, assuming the host application is rendered at `https://ebay.com/n/all-categories`, the embedded application will resolve to `https://ebay.com/my-nested-app`.
## `headers`
Optionally provide additional http headers to send. Only the object form shown below is supported.
```marko
```
> Note that be default on the server side headers are copied from the current incoming request, the `headers` option will be merged with existing headers.
## `cache`
Mirrors the [`Request.cache` options](https://developer.mozilla.org/en-US/docs/Web/API/Request/cache) (works on both server and client renders).
```marko
```
## `fetch`
Optionally provide function to override default `fetch` logic.
```marko
```
## `timeout`
A timeout in `ms` (defaults to 30s) that will prematurely abort the request. This will trigger the `<@catch>` if provided.
If set to `0` the request will not time out.```marko
```
## `<@catch|err|>`
An [attribute tag](https://markojs.com/docs/syntax/#attribute-tag) rendered when there is a network error or timeout.
If there is no `@catch` handler the error will be emitted to the stream, similar to the [``](https://markojs.com/docs/core-tags/#await) tag.```marko
<@catch|err|>
error: ${err.message}
@catch>```
## `<@loading>`
An [attribute tag](https://markojs.com/docs/syntax/#attribute-tag) rendered when while the request is still being streamed.
It is removed after the request has either errored, or successfully loaded.```marko
<@loading>
We are loading the nested app...
@loading>```
## `class`
Optional `class` attribute which works the same way as [Marko class attribute](https://markojs.com/docs/syntax/#class-attribute).
```marko
```
## `style`
Optional `style` attribute which works the same way as [Marko style attribute](https://markojs.com/docs/syntax/#style-attribute).
```marko
```
## `client-reorder`
Similar to the [`` tag client-reorder attribute](https://markojs.com/docs/core-tags/#await), this tells the micro-frame to avoid blocking content later in the document.
> Note when this is used the micro-frame will be buffered instead of streamed and inserted once it's ready.
# Communicating between host and child
Communicating with the embedded application happens primarily in one of two ways, either you want to do a full reload of and get new HTML, or you want to orchestrate a client side rendered update.
### Full reload
To perform a full reload of the embedded application it works best to pass a query string in the `src` attribute. Whenever `src` updates, a full reload will happen automatically.
```marko
class {
onCreate() {
this.state = { page: 0 };
}nextPage() {
this.state.page++;
}
}Next Page
```With the above, any time `state.page` changes the `my-nested-app` content will be re-loaded.
### Client side update
Client side communication between the host and child application can be done through a number of mechanisms.
You can use a global store, store data on the dom (perhaps even use web components) or other creative options.You can do this relatively simply by having a contract between the host and child application.
Below is an example using a global exposed by the nested application.```marko
class {
onCreate() {
this.state = { page: 0 };
}openModal() {
if (window.nestedApp) {
window.nestedApp.openModal();
}
}
}Open nested app modal
```# Code of Conduct
This project adheres to the [eBay Code of Conduct](/.github/CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.