Single-SPA Parcels and vite-plugin-single-spa
Tags: singlespa, frontend, webdev, react
Published: 2023-11-12 19:45:26

> **UPDATE**:  `vite-plugin-single-spa` v0.4.0 is out and is compatible with parcels.

Welcome, everyone, to the next topic in the `single-spa` arena: **Parcels**. These are pieces of user interfaces that are meant to be used as "utilities" from anywhere in your application, and since your application is built by glueing together mini applications (micro-frontends), it means that parcels are meant to be used by any micro-frontend, regardless of the parcel framework or the micro-frontend framework.

## What This Article is Not About

I apologize if I made you believe I was going to give a tutorial on `single-spa` parcels. I will not. However, that doesn't necessarily mean that this article won't have explanations around how to create them. It will. It is just that the article's objective is not to be a tutorial on `single-spa` parcels.

Instead, this article will talk about parcels in the context of the `vite-plugin-single-spa` plug-in and the general requirements to make projects that export them, from start to finish.

## Setting Up Test Projects

Ok, since my interest in this topic involves "old" *React* code and new *Svelte* code, I'll be focusing on this combination.

Let's create a *React* root project and a *Svelte* parcels project, which would be a micro-frontend type project.

The root project:

```plaintext
PS C:\Users\webJo\src> npm create vite@latest
√ Project name: ... ReactRoot
√ Package name: ... reactroot
√ Select a framework: » React
√ Select a variant: » TypeScript + SWC

Scaffolding project in C:\Users\webJo\src\ReactRoot...

Done. Now run:

  cd ReactRoot
  npm install
  npm run dev
```

And then the parcels project:

```plaintext
PS C:\Users\webJo\src> npm create vite@latest
√ Project name: ... SvelteParcels
√ Package name: ... svelteparcels
√ Select a framework: » Svelte
√ Select a variant: » TypeScript

Scaffolding project in C:\Users\webJo\src\SvelteParcels...

Done. Now run:

  cd SvelteParcels
  npm install
  npm run dev
```

Great! In the root project, let's install the needful:

```bash
npm i vite-plugin-single-spa bootstrap single-spa single-spa-react
npm i -D sass
```

Let's do similarly in the parcels project:

```bash
npm i vite-plugin-single-spa single-spa-svelte
npm i -D bootstrap sass
```

Excellent. We can start.

### Root Project Code

First, delete `App.css` and `index.css` and instead add `src/App.scss`. Remove all references to the deleted files (use the *Find in Files* functionality of your code editor). Then add `import './app.scss';` to `src/App.tsx`.

Now create appropriate styling and new content for the `App` component.

This is `src/App.scss`:

```scss
@import "../node_modules/bootstrap/scss/bootstrap";

html {
    --main-color: rgb(73, 13, 130);
}

div.app {
    height: 100vh;
    display: flex;
    align-items: center;
    width: 100%;
}

.content {
    border-radius: 0.5em;
    padding: 1.5em;
    height: 100%;
}

.root-content {
    @extend .content;
    background-color: var(--main-color);
    color: white;

    & > h1 > span {
        margin-right: 0.3em;
    }
}

.parcel-content {
    @extend .content;
    border: 0.25em dashed (var(--main-color));
}
```

This is `src/App.tsx`:

```typescript
import { useState } from 'react'
import reactLogo from './assets/react.svg'
// @ts-expect-error
import Parcel from 'single-spa-react/parcel'
import './App.scss'

function App() {
  const [loadParcel, setLoadParcel] = useState(false);

  return (
    <div className="app">
      <div className="row w-100">
        <div className="col-sm-5">
          <div className="root-content">
            <h1><span><img src={reactLogo} alt="React" /></span>React Root Project</h1>
            <p>
              Click the button below to load a Svelte parcel.
            </p>
            <button type="button" className="btn btn-primary">Toggle Parcel</button>
          </div>
        </div>
        <div className="col-sm-7">
          <div className="parcel-content">
            <h3>Parcel Display</h3>
            {loadParcel ? <Parcel /> : null}
          </div>
        </div>
      </div>
    </div>
  )
}

export default App
```

You should now see a root project similar to this:


![Root project appearance.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4gaylgz1teo1dl7p11ej.png)

Looking good so far. Let's stop for a moment here with the root project because now we need to know about our parcel, and that's the other (*Svelte*\-powered) project.

### Parcel Project Code

We start cleaning up. Delete the `src/App.css` file, and remove its reference in `src/main.ts`. Theoretically speaking, we should just be able to create the parcel component(s) we want without worrying about the index page or the `src/main.ts` script. However, if you would like to conserve the ability to see what you will mount in the root project without mounting in the root project, we can set something up for sure.

Since we are using *Bootstrap* here, let's create `src/App.scss` to get ourselves a copy of *Bootstrap* ready to go in the test page:

```scss
@import "../node_modules/bootstrap/scss/bootstrap";
```

Then add `import './App.scss';` to `src/main.ts`. That should be it.

Now, unlike "regular" micro-frontends, we won't be exporting `single-spa` lifecycle functions that mount the `App` *Svelte* component. No sir! We will now mount potentially multiple *Svelte* components, each requiring `single-spa` lifecycle functions.

So `src/App.svelte` is only a concern when it comes to testing without mounting. Let's create a component that we can mount as a parcel now.

Add the file `src/lib/Welcome.svelte`:

```xml
<script lang="ts">
    export let name: string | undefined = undefined;
</script>

<div>
    <p>Welcome, <span class="text-primary">{name ? name : 'person or thing'}!</span></p>
    <button class="btn btn-secondary">{name ? 'Sign out' : 'Sign in'}</button>
</div>
```

This is a very simple component that welcomes the user. It has a single prop named `name`, and if no name is provided, we assume the user hasn't logged in. All styling is done through *Bootstrap* classes.

To see how it looks like in the test page, simply reduce `src/App.svelte` to this:

```xml
<script lang="ts">
    import Welcome from './lib/Welcome.svelte';
</script>

<main>
  <Welcome />
</main>
```

This should show you this:


![Welcome Svelte parcel.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a7j3jckh79a8mph890u2.png)

I intentionally added part of *Microsoft Edge*'s border and toolbar to remind us all how lacking is our project right now in terms of styling that our content is completely against the browser's edges. However, let's remind ourselves that we want mountable parcels, so this should not be a primary concern for us at this point in time.

---

Ok, let's call the initial setup of both projects complete at this point. We shall now proceed to export the *Svelte*\-powered `single-spa` parcel.

## Exporting Parcels

According to `single-spa`'s documentation, we export a parcel very similarly to how we export micro-frontends. We are even encouraged to use the same helpers (`single-spa-react`, `single-spa-svelte`, etc.).

Add the file `src/parcels.ts` to the **SvelteParcels** project:

```typescript
import Welcome from "./lib/Welcome.svelte";
// @ts-expect-error
import singleSpaSvelte from 'single-spa-svelte';

export const welcomeParcel = singleSpaSvelte({
    component: Welcome
});
```

Modify `vite.config.ts`:

```typescript
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import vitePluginSingleSpa from 'vite-plugin-single-spa'

export default defineConfig({
  plugins: [svelte(), vitePluginSingleSpa({
    serverPort: 4201,
    spaEntryPoint: 'src/parcels.ts'
  })],
});
```

Note: This changes our *Vite* server port! If your test page stopped working, note the new port value.

At this point, I am confident we successfully exported the `Welcome` *Svelte* component as a `single-spa` parcel. Let's go back to the root project.

## Importing Parcels

Let's now take care of the configuration of the root project. We haven't added any of the `single-spa` things that we need.

Start by adding `src/importMap.json`:

```json
{
    "imports": {
        "@test/parcels": "http://localhost:4201/spa.js"
    }
}
```

Then, `src/importMap.dev.json`:

```json
{
    "imports": {
        "@test/parcels": "http://localhost:4201/src/parcels.ts"
    }
}
```

These should be familiar to you: It is almost identical to what we set up for micro-frontends.

Now, modify `vite.config.ts`:

```typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import vitePluginSingleSpa from 'vite-plugin-single-spa'

export default defineConfig({
  plugins: [react(), vitePluginSingleSpa({
    type: 'root',
    imo: '3.1.0'
  })],
})
```

So far, so good. Now to `src/App.tsx` to make the "Toggle Parcel" button work.

In case you didn't realize, we installed `single-spa-react` in the root project. Why? Because it comes with a *React* component called `Parcel` that can be used to easily mount `single-spa` parcels. We even added it to the markup very early on. Its [documentation](https://single-spa.js.org/docs/ecosystem-react#parcels) tells us that we must provide the `config` prop at the very least. Its value must be a `parcelConfig` object, or a loader function that returns said object asynchronously. Let's use the latter as it seems simpler because we won't have to work magic around the statement `import { welcomeParcel } from '@test/parcels';` when running in development mode.

Ok, allow me to show the code for `src/App.tsx` with the modifications:

```typescript
import { useState } from 'react'
import reactLogo from './assets/react.svg'
// @ts-expect-error
import Parcel from 'single-spa-react/parcel'
import './App.scss'

function App() {
  const [loadParcel, setLoadParcel] = useState(false);
  const parcelModuleName = "@test/parcels";

  async function loadWelcomeParcel() {
    const parcelsModule = await import(/* @vite-ignore */ parcelModuleName);
    return parcelsModule.welcomeParcel;
  }

  return (
    <div className="app">
      <div className="row w-100">
        <div className="col-sm-5">
          <div className="root-content">
            <h1><span><img src={reactLogo} alt="React" /></span>React Root Project</h1>
            <p>
              Click the button below to load a Svelte parcel.
            </p>
            <button
              type="button"
              className="btn btn-primary"
              onClick={() => setLoadParcel(v => !v)}
            >
              Toggle Parcel
            </button>
          </div>
        </div>
        <div className="col-sm-7">
          <div className="parcel-content">
            <h3>Parcel Display</h3>
            {loadParcel ? <Parcel
              config={loadWelcomeParcel}
              handleError={console.error}
              parcelDidMount={() => console.debug('Parcel mounted.')}
            /> : null}
          </div>
        </div>
      </div>
    </div>
  )
}

export default App
```

The modifications are rather minimal:

* We defined the module name in a constant for two reasons: The first one is that *Vite* will complain if we put it directly inside the dynamic `import()` call; the second one is that we can use it in other loader functions as we add parcels to our parcels project. I don't know if I will do it, but I thought I might.
    
* We defined the `loadWelcomeParcel()` function that imports and returns the named `welcomeParcel` export.
    
* We provided functionality to the "Toggle Parcel" button we had.
    
* We gave the `Parcel` component the needed properties, which are:
    
    * `config`: The loader function we created.
        
    * `handleError`: We added the `console.error` method as our error handler.
        
    * `parcelDidMount`: We added a simple logging statement for good measure.
        

Great!, save, click the "Toogle Parcel" button and .... doesn't work! Nothing happens. Well, it took me some time to figure this one out. The issue has been raised as a bug since Jaunuary 13, 2023, [here](https://github.com/single-spa/single-spa-react/issues/160). I'm no React developer, so I don't know if what I'm about to say is bad: Solve this by removing `<React.StrictMode>` from `src/main.tsx`. If this is a bad thing, please, by all means don't take it against me! Go to the issue's page and bombard it with comments and upvotes so it gets fixed. Even better: If you can fix it, fix it and make a pull request for it!

Ok, back to topic. This is how I have `src/main.tsx` after removing `<React.StrictMode>`:

```typescript
import ReactDOM from 'react-dom/client'
import App from './App.tsx'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <App />
)
```

Just the `App` component. Ok! Let's retry! Another error, that we see twice (once for our error handler, and once for the uncaught nature of the error):

> Error: During 'mount', parcel threw an error: was not passed a mountParcel prop, nor is it rendered where mountParcel is within the React context. If you are using within a module that is not a single-spa application, you will need to import mountRootParcel from single-spa and pass it into as a mountParcel prop

This one can be understood easily: The `Parcel` component looks for the `mountParcel` function inside the `SingleSpaContext` context that `single-spa-react` provides to micro-frontends. However, since I'm a stubborn child that doesn't follow `single-spa`'s recommendations, I have a root *React* project that wants to load a parcel. So how do I fix? Very simply: We use `single-spa`'s `mountRootParcel()` function. Just import this function and add it to the `Parcel` component as a prop:

```typescript
// This is src/App.tsx, just in case.

// Other imports here...
import { mountRootParcel } from 'single-spa'
// Rest of code here, up to the Parcel component...

            {loadParcel ? <Parcel
              config={loadWelcomeParcel}
              mountParcel={mountRootParcel}
              handleError={console.error}
              parcelDidMount={() => console.debug('Parcel mounted.')}
            /> : null}
// Rest of file here...
```

Save and try again. Success! Kind of. Something unexpected is in sight. Let me show you a screenshot:

![Svelte parcel inside the root project's page with an unexpected name.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j9pt5oe3gabytiwd4eje.png)

Did you see? The name! It says "Welcome, **parcel-0**"! I was expecting "Welcome, **person or thing**!" which is what I programmed for the cases where the `name` prop was not specified. Furthermore, the button's caption is "Sign out", not "Sign in". One more learning, I suppose: One of the properties injected by `single-spa` to parcels is the `name` property, and its value is an automatically-assigned name. We need to avoid using `name` as a component prop, unless we are after the parcel's name.

Now that we are speaking about `single-spa` props sent to parcels, let's complete the list easily by looking at the warnings in the console:

![Warnings about unexpected component props.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gmy35qhlsr2wd6n8mchg.png)

This means that the complete list of props a parcel receives is:

| Name | Description |
| --- | --- |
| `name` | The parcel's name. |
| `domElement` | The element where the parcel is mounted. |
| `mountParcel` | A function that can be used to mount parcels. At this point in time, I am unsure if this is `mountRootParcel`, or something else. |
| `singleSpa` | The `singleSpa` instance object; same as in micro-frontends. |
| `unmountSelf` | A function that unmounts the parcel, made available to the parcel. |

This list of properties does not seem to be anywhere in the `single-spa`'s official documentation website, so you might want to bookmark this article.

Excellent progress! We have managed to export parcels from *Svelte* and use them in *React*. Let's try to interact with the parcel from *React*.

## Interacting With Parcels

Due to our unexpected outcome, let's first rename the `name` prop in the `Welcome` parcel to `user`:

```xml
<script lang="ts">
    export let user: string | undefined = undefined;
</script>

<div>
    <p>Welcome, <span class="text-primary">{user ? user : 'person or thing'}!</span></p>
    <button class="btn btn-secondary">{user ? 'Sign out' : 'Sign in'}</button>
</div>
```

Because of HMR, saving this immediately shows up correctly inside the *React* page.

Ok, let's add a text box for us to type a user from *React* and have *React* send this to the `Welcome` parcel as the user's name.

```typescript
  // This is in src/App.tsx:
  // One more ugly state for the user:
  const [user, setUser] = useState<string | undefined>(undefined);

  // Then the modification in markup, including passing the prop to the parcel.
  <h3>Parcel Display</h3>
  <div className="mb-3">
    <label htmlFor="user">User's name:</label>
    <input className="form-control" type="text" id="user" onInput={(e) => setUser(e.currentTarget.value)} />
  </div>
  {loadParcel ? <Parcel
    config={loadWelcomeParcel}
    mountParcel={mountRootParcel}
    handleError={console.error}
    parcelDidMount={() => console.debug('Parcel mounted.')}
    user={user}
  /> : null}
```

Save and test:

![React component updating Svelte parcel's properties.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/abagzckg83723q6sqdks.png)

Aw, stop! You're too kind. But, yes, yes I am.

In all seriousness now: The coupling works seamlessly, where the *Svelte* component quickly updates its state on every keystroke. There is no functional difference between this parcel and a component made in *React* in the same root project.

Let's now do the inverse: Have the *Svelte* parcel interact with the *React* code. For this, we will add a handler to the `click` event of the button in the parcel.

Because *React* doesn't work with events, the simplest is to provide a prop for a callback in the *Svelte* side. Add a new prop to the `Welcome` component, then add a handler function, and finally, inside the handler, both dispatch the event and call the callback prop:

```typescript
// Import the usual createEventDispatcher and create the dispatch function:
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
...
// The new prop:
export let onSignInOut: Function | undefined = undefined;
...
// The handler:
function signInOutHandler() {
    const detail = {
        user,
        signedIn: !!user
    };
    dispatch('signInOut', detail);
    (onSignInOut ?? (() => {}))(detail);
}
```

Finally, modify the button so it runs the handler on click:

```xml
<button class="btn btn-secondary ms-auto" on:click={signInOutHandler}>{user ? 'Sign out' : 'Sign in'}</button>
```

Now to the **ReactRoot** project, where we add the extra prop to the Parcel component in `src/App.tsx`:

```typescript
{loadParcel ? <Parcel
  config={loadWelcomeParcel}
  mountParcel={mountRootParcel}
  handleError={console.error}
  user={user}
  onSignInOut={(e: any) => console.debug('Sign In/Out: %o', e)}
/> : null}
```

Test this by loading the parcel with the "Toggle Parcel" button, then clicking the "Sign In" button. The console should show you the data received in the call back. Very simple and straightforward.

> **NOTE**: We did the event and the callback in Svelte for maximum compatibility. Maybe Svelte v5 surprises us with bubbling Svelte events in the future. If that's the case, maybe we can switch to consuming the event in a parent element of the `Parcel` component. We'll see.

We have covered the basic functionality of parcels with great success so far, but one important topic remains: The parcel's CSS.

## CSS in Parcels

So far, our parcel has only had *Bootstrap* CSS classes applied, and since our root project provides *Bootstrap*, we haven't noticed any problems in the styling department. This demonstrates how useful a shared styleguide is when working with micro-frontends. But let's be honest: Real-life applications will more than likely need more CSS. So let's explore what happens in our current set up when we add styling to the *Svelte* component.

Before we start, go ahead and build the **SvelteParcels** project. You'll see its output consists of a single file: `spa.js`:

```plaintext
vite v4.5.0 building for production...
✓ 27 modules transformed.
dist/spa.js  6.65 kB │ gzip: 2.81 kB
✓ built in 299ms
```

There is no CSS needed for injection. This parcel works out of the box without us having to worry about CSS files.

So, back to topic: Let's pretend for a moment that *Bootstrap* doesn't provide utility classes, or that I simply don't know that I can do `class="d-flex flex-nowrap align-items-baseline gap-3 w-100"`. So let's make the *Svelte* component line things up in one line.

Modify `src/lib/Welcome.svelte` like below:

```xml
<script lang="ts">
    export let user: string | undefined = undefined;
</script>

<div class="welcome">
    <p>Welcome, <span class="text-primary">{user ? user : 'person or thing'}!</span></p>
    <button class="btn btn-secondary ms-auto">{user ? 'Sign out' : 'Sign in'}</button>
</div>

<style>
    div.welcome {
        display: flex;
        flex-flow: row nowrap;
        gap: 1em;
        align-items: baseline;
        width: 100%;
    }
</style>
```

The only things that changed are that we applied a CSS class to the component's root `DIV` element, that we added automatic margin to the button to push it to the right, and of course, the main thing here: The `style` tag.

As soon as you save, the changes can be seen in the **SvelteParcels** test page and the **ReactRoot** page. This happens because *Vite* dev server injects the CSS for us. If you don't believe, just examine the contents of the `HEAD` HTML element. The last element will be:

```xml
<style type="text/css" data-vite-dev-id="C:/Users/webJo/src/SvelteParcels/src/lib/Welcome.svelte?svelte&amp;type=style&amp;lang.css">div.welcome.s-U5UaycqR-l4Y{display:flex;flex-flow:row nowrap;gap:1em;align-items:baseline;width:100%}.s-U5UaycqR-l4Y{}</style>
```

This luxury is gone in a built project, so we can expect to lose our gains if we were to run the projects in preview mode. Let's.

This is my build of **SvelteParcels**:

```plaintext
vite v4.5.0 building for production...
✓ 28 modules transformed.
dist/assets/vpss(svelteparcels)parcels-2d7e02d5.css  0.10 kB │ gzip: 0.11 kB
dist/spa.js                                          6.69 kB │ gzip: 2.83 kB
✓ built in 300ms
```

This is the build for **ReactRoot**, although of no particular importance to the topic at hand:

```plaintext
vite v4.5.0 building for production...
✓ 36 modules transformed.
dist/index.html                   0.90 kB │ gzip:  0.50 kB
dist/assets/react-35ef61ed.svg    4.13 kB │ gzip:  2.14 kB
dist/assets/index-37125051.css  226.14 kB │ gzip: 30.93 kB
dist/assets/index-c8b3e608.js   169.48 kB │ gzip: 54.44 kB
✓ built in 2.94s
```

> Unrelated note: Amazing how heavy React is. Svelte, I shall be with you until my final days!

Now do `npm run preview` on both projects, then open **ReactRoot**'s page. Sure enough, the scoped styling in the *Svelte* component is gone.

### Using cssLifecycle from vite-plugin-single-spa

We solved the CSS mounting and unmounting of micro-frontends some time ago with a dynamic ES module called `vite-plugin-single-spa/ex` that provides the `cssLifecycle` object. This is used when exporting the `single-spa` lifecycle functions, and we can give it a go right now.

Open `src/parcels.ts` in the **SvelteParcels** project and modify it to look like this:

```typescript
import Welcome from "./lib/Welcome.svelte";
// @ts-expect-error
import singleSpaSvelte from 'single-spa-svelte';
import { cssLifecycle } from 'vite-plugin-single-spa/ex';

const lc = singleSpaSvelte({
    component: Welcome
});

export const welcomeParcel = {
    bootstrap: [cssLifecycle.bootstrap, lc.bootstrap],
    mount: [cssLifecycle.mount, lc.mount],
    unmount: [cssLifecycle.unmount, lc.unmount],
    update: lc.update
};
```

Now build **SvelteParcels** one more time, but remember to add *Vite*'s base property to `vite.config.ts` first, so the `Link` HTML element wil contain the correct URL, like this:

```typescript
export default defineConfig({
  plugins: [svelte(), vitePluginSingleSpa({
    serverPort: 4201,
    spaEntryPoint: 'src/parcels.ts'
  })],
  base: 'http://localhost:4201' // <----  THIS!!!!
});
```

Build one more time, then refresh the **ReactRoot**'s webpage. Now the style is in.

Although it might seem that we have all figured out, in reality we are far from correctness. To demonstrate this easily, let's duplicate the user text box and the parcel in the **ReactRoot** project, so we can see two instances of the `Welcome` parcel simultaneously.

---

### Bug Found in single-spa-svelte!

The code I initially wrote for this part of the article revealed a problem with the current implementation of the `singleSpaSvelte()` function in the `single-spa-svelte` NPM package that prevents us from creating multiple instances of the same parcel. Long story short, we will be working the issue around.

If you are interested in the topic, I suggest you visit the [issue I raised in GitHub](https://github.com/single-spa/single-spa-svelte/issues/28).

---

Ok, because of the bug in `single-spa-svelte`, let's do some modifications to what we have right now. Let's open `src/parcels.ts` in the **SvelteParcels** project and modify it as follows:

```typescript
import Welcome from "./lib/Welcome.svelte";
// @ts-expect-error
import singleSpaSvelte from 'single-spa-svelte';
import { cssLifecycle } from 'vite-plugin-single-spa/ex';


export function welcomeParcel() {
    const lc = singleSpaSvelte({
        component: Welcome
    });
    return {
        bootstrap: [cssLifecycle.bootstrap, lc.bootstrap],
        mount: [cssLifecycle.mount, lc.mount],
        unmount: [cssLifecycle.unmount, lc.unmount],
        update: lc.update
    };        
}
```

The short explanation is that we are now exporting a factory function that creates new parcel configuration objects whenever it is evaluated. Before, we were exporting a single parcel configuration object. Factories to the rescue!

Rebuild the **SvelteParcels** project, and then restart the preview *Vite* server.

Now let's modify `src/App.tsx` in **ReactRoot** one more time. The workaround for the bug required only one change (highlighted with a comment below). The rest of the changes pertain to the duplication of the parcel (second set of state data and markup):

```typescript
import { useState } from 'react'
import reactLogo from './assets/react.svg'
// @ts-expect-error
import Parcel from 'single-spa-react/parcel'
import './App.scss'
import { mountRootParcel } from 'single-spa'

function App() {
  const [loadParcel, setLoadParcel] = useState(false);
  const [loadParcel2, setLoadParcel2] = useState(false);
  const [user, setUser] = useState<string | undefined>(undefined);
  const [user2, setUser2] = useState<string | undefined>(undefined);
  const parcelModuleName = "@test/parcels";

  async function loadWelcomeParcel() {
    const parcelsModule = await import(/* @vite-ignore */ parcelModuleName);
    return parcelsModule.welcomeParcel(); // <----- HERE!  Now welcomeParcel is a function that must be called.
  }

  return (
    <div className="app">
      <div className="row w-100">
        <div className="col-sm-5">
          <div className="root-content">
            <h1><span><img src={reactLogo} alt="React" /></span>React Root Project</h1>
            <p>
              Click the button below to load a Svelte parcel.
            </p>
            <button
              type="button"
              className="btn btn-primary"
              onClick={() => setLoadParcel(v => !v)}
            >
              Toggle Parcel
            </button>
            <button
              type="button"
              className="btn btn-primary ms-2"
              onClick={() => setLoadParcel2(v => !v)}
            >
              Toggle Parcel 2
            </button>
          </div>
        </div>
        <div className="col-sm-7">
          <div className="parcel-content">
            <h3>Parcel Display</h3>
            <div className="mb-3">
              <label htmlFor="user">User's name:</label>
              <input className="form-control" type="text" id="user" onInput={(e) => setUser(e.currentTarget.value)} />
            </div>
            {loadParcel ? <Parcel
              config={loadWelcomeParcel}
              mountParcel={mountRootParcel}
              handleError={console.error}
              user={user}
            /> : null}
            <h3>Parcel Display 2</h3>
            <div className="mb-3">
              <label htmlFor="user2">User's name:</label>
              <input className="form-control" type="text" id="user2" onInput={(e) => setUser2(e.currentTarget.value)} />
            </div>
            {loadParcel2 ? <Parcel
              config={loadWelcomeParcel}
              mountParcel={mountRootParcel}
              handleError={console.error}
              user={user2}
            /> : null}
          </div>
        </div>
      </div>
    </div>
  )
}

export default App
```

Re-build **ReactRoot** and restart the *Vite* preview server. Reload the webpage.

Start playing around with the "Toggle Parcel" and "Toggle Parcel 2" buttons. Notice any issues? After having both parcel instances on screen, hiding one of them also unmounts the parcel's CSS, making the remaining on-screen instance lose its correct appearance.

This behavior will also be present when simultaneously loading more than one parcel from the same parcel project, even if they are of different components. The current iteration of `vite-plugin-single-spa` (version **0.3.1**) is **unfit for parcels**. Sad face.

## Future Steps

Until `vite-plugin-single-spa` is updated to account for the parcels scenario, you will have to come up with your own CSS mounting/unmounting algorithm for your parcels project, or look for a solution elsewhere. The good news is that I, the author of `vite-plugin-single-spa`, will be actively working on this during the next week. You should be able to see **v0.4.0** or greater coming up soon.

### Potential Implementations

I'll confess I started writing this article knowing very well that my plug-in's `cssLifecycle` object was going to fail. This means that I have already thought about a few possible solutions. I'll explain now the two that appeal to me the most right now.

The first possible solution adds a new property to the options for micro-frontends:

```typescript
    /**
     * Defines the plugin options for Vite projects that are single-spa micro-frontentds.
     */
    export type SingleSpaMifePluginOptions = {
        /**
         * The type of single-spa project (micro-frontend or root).
         */
        type?: 'mife';
        /**
         * The server port for this micro-frontend.
         */
        serverPort: number;
        /**
         * The path to the file that exports the single-spa lifecycle functions.
         */
        spaEntryPoint?: string;
        /**
         * Unique identifier given to the project.  It is used to tag CSS assets so the cssLifecyle object in 
         * the automatic module "vite-plugin-single-spa/ex" can properly manage the CSS lifecycle.
         * 
         * If not provided, the project's name (up to the first 20 letters) is used as identifier.
         */
        projectId?: string;
        /**
         * CSS strategy to be used by the cssLifecycle object in the extensions module.
         */
        cssStrategy: 'singleMife' | 'multiMife' // <----- THIS ONE !!!
    };
```

This new `cssStrategy` property will govern the CSS mounting algorithm to use. The first option, `singleMife`, would apply the logic that currently exists in **v0.3.1**. The second option, `multiMife`, would apply a similar logic, but this time using a counter that increases every time a parcel/micro-frontend is mounted, and decreases every time a parcel/micro-frontend is unmounted. Only if the counter reaches zero, the algorithm disables (unmounts) the HTML CSS `Link` elements in the `HEAD` element.

Why not simply upgrade to the counter version? The counter version would be incompatible with `single-spa`'s unloading mechanism in the sense that `cssLifecycle.bootstrap()` currently resets the `HEAD` element by wiping clean any previously injected CSS `Link` elements. My experience with `single-spa` is yet very limited, and `vite-plugin-single-spa` hasn't reached a massive user audience yet; this might become important for users at some point in time. I just don't know if it is safe to forgo this reset feature because loading multiple `single-spa` lifecycle objects (be them parcels or micro-frontends) causes trouble with the current algorithm that doesn't expect `bootstrap()` to be called multiple times. This means that I must move the current logic inside `bootstrap()` to the module itself so it only runs once, so bye-bye to the CSS resetting of the `HEAD` element. Giving consumers the choice of strategy allows users to opt for one or another more comfortably.

Also, unloading modules exist to support potential HMR of parcel modules. Without the wiping of CSS `Link` elements, HMR might suffer.

The second solution that appeals to me is to create a parcel-specific project type with very specific rules about how to code it.

```typescript
    /**
     * Defines the plugin options for Vite projects that are single-spa parcels.
     */
    export type SingleSpaParcelsPluginOptions = {
        type?: 'parcels';
        /**
         * The server port for this micro-frontend.
         */
        serverPort: number;
        parcelEntryPoints?: string | string[];
    };
```

Almost identical to the micro-frontend type, this type allows multiple entry points, but expects that the project:

1. Only exports parcels. It cannot export a micro-frontend.
    
2. Only exports one parcel per entry point.
    
3. Dynamic import of components is off the table because along with the JS splitting comes CSS splitting. This extra piece of CSS cannot be accounted for by my plug-in because it won't be listed in the list of CSS bundles for the entry point chunk and therefore cannot be managed.
    

With these guarantees in place, I can provide a CSS mounting algorithm that uses counters per CSS bundle to account for multiple instances of the same parcel and to account for any shared CSS bundles that may be needed by different parcels that might be present in the page simultaneously. The `cssLifecycle` object would need to be told the parcel name so it uses the correct list of CSS bundle filenames.

---

What do you think? Can you come up with a better solution? Let me know in the comments! I would appreciate the extra help.

## Conclusion

Well, this was certainly a long journey. I think the best we can do right now is to summarize our learnings:

* There is very little difference between a parcel and a micro-frontend.
    
* Although you might hear that `single-spa` informally can load multiple instances of the same parcel/micro-frontend (in `single-spa`'s slack, mostly), the reality is that this was never a design objective of the library. Attempting this requires thorough testing and probably a factory function here and there.
    
* `single-spa-react` has a very handy `Parcel` component that simplifies the consumption of parcels, although it doesn't work with *React*'s strict mode, most likely due to the double-render that it introduces.
    
* The properties injected by `single-spa`'s `mountParcel()` and `mountRootParcel()` are: `name`, `domElement`, `mountParcel`, `singleSpa` and `unmountSelf`. We must not use these names for other props in the component, or things will get messy. Furthermore, we cannot use as prop names the names of the props of the `Parcel` component, such as `config`, or `wrapWith`.
    
* `single-spa-svelte` has a confirmed bug that prevents it to properly create multiple instances of a parcel/micro-frontend. Wrap the call to `singleSpaSvelte()` inside a factory function as a workaround.
    
* `vite-plugin-single-spa` **v0.3.1** is unfit for parcels in general. Wait for a newer version to come out.
    

That is all for today. Let me know if you have questions or would like me to cover a specific scenario or topic around `single-spa`, my plug-in or any combination of frameworks. I'll try to comply.

Happy coding!