Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot properly create multiple instances of the same parcel #28

Open
webJose opened this issue Nov 12, 2023 · 2 comments
Open

Cannot properly create multiple instances of the same parcel #28

webJose opened this issue Nov 12, 2023 · 2 comments

Comments

@webJose
Copy link

webJose commented Nov 12, 2023

Hello, everyone.

I am experimenting with parcels and I stumbled upon a problem: singleSpaSvelte() cannot properly handle multiple instances of the same parcel.

The problem lies in the general use of mountedInstances.instance. The code can only track the most recent instance created.

I can make the PR for this, I suppose. Do let me know.

@webJose
Copy link
Author

webJose commented Nov 12, 2023

Workaround:

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
    };        
}

This code sample would be the entry point being exported by the Svelte project. Instead of exporting an object that has the single-spa lifecycle functions, we export a factory function. This will effectively work around the problem because now every new object will have its own mountedInstances variable.

@joeldenning : This is one good example of why the factory pattern I suggested might be a better approach. Multiple instances of parcels/micro-frontends is flaky without it.

@webJose
Copy link
Author

webJose commented Nov 12, 2023

Oh, I forgot the consumption example.

I have a React consumer of the parcel. This is the complete code of src/App.tsx:

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();
  }

  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

The loadWelcomeParcel() evaluates the exported welcomeParcel function, creating truly isolated instances. The Parcel component simply picks up on a new instance every time, and at different places.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant