An open API service indexing awesome lists of open source software.

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.

Awesome Lists containing this project

README

        





@micro-frame/marko



Styled with prettier







NPM Version



Downloads


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.