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

Optimize Single-SPA Layout to Avoid Repeated Application Mounts Using Route Grouping or Regex Matching #215

Open
ddavid93 opened this issue Jan 9, 2025 · 8 comments

Comments

@ddavid93
Copy link

ddavid93 commented Jan 9, 2025

Currently, in our microfrontend layout, repeated applications like the navbar are being mounted and unmounted for every route change (/a, /b, /c). This creates unnecessary performance overhead, as the navbar should persist across these routes.

Problem Details:
The existing layout structure mounts @company/navbar individually for each route, as shown below:

<route path="a">
  <nav class="navbar">
    <application name="@company/navbar"></application>
  </nav>
  <main class="content ynv-app">
    <application name="@company/a"></application>
  </main>
</route>

<route path="b">
  <nav class="navbar">
    <application name="@company/navbar"></application>
  </nav>
  <main class="content ynv-app">
    <application name="@company/b"></application>
  </main>
</route>

Instead, we need a more efficient approach where the navbar can persist across specific routes (/a, /b, /c), reducing redundant re-renders and remounts.

Proposed Solution:
We could use a route grouping or regex-based routing structure to share components like the navbar between multiple routes, as shown in the example below:

<route path="a|b|c">
  <nav class="navbar">
    <application name="@company/navbar"></application>
  </nav>
  <main class="content ynv-app">
    <route path="a">
      <application name="@company/a"></application>
    </route>
    <route path="b">
      <application name="@company/b"></application>
    </route>
    <route path="c">
      <application name="@company/c"></application>
    </route>
  </main>
</route>

This approach would:
1- Reduce Redundancy: Prevent remounting of shared applications like navbar for every route.
2- Maintain Isolation: Each microfrontend (a, b, c) remains independent within its respective route, ensuring compatibility with different Vue versions.

Challenges:
The current Single-SPA Layout setup does not support route grouping with regex out of the box.
Moving the navbar outside the route hierarchy is not viable due to compatibility issues across Vue versions in different microfrontends.

Additional Notes:
If regex-based routing is not feasible, consider alternative solutions, such as a configuration option to mark specific applications as persistent across routes.

@joeldenning
Copy link
Member

Ideally single-spa-layout would do complete dom diffing to avoid unnecessary unmounting/remounting in all situations. It currently does so in some situations, but not all.

In this specific case, though, I believe that putting the <route> element lower in the dom tree would solve the problem. See the following alternative layout definition, which I believe would solve the problem without any changes to single-spa-layout:

<nav class="navbar">
  <application name="@company/navbar"></application>
</nav>
<main class="content ynv-app">
  <route path="a">
    <application name="@company/a"></application>
  </route>
  <route path="b">
    <application name="@company/b"></application>
  </route>
</main>

Closing since I believe the proposed layout definition would solve this problem

@ddavid93
Copy link
Author

Hi @joeldenning

Thank you for your response! Unfortunately, we cannot use that solution because we have two types of microfrontends: Vue 2 apps and Vue 3 apps, and we have the both routes in a same layout.html

When we mount Vue 2 apps, we load the CSS files specific to them, and the same applies to Vue 3 apps. Below, I’m sharing an example of our layout.html. I’ve renamed the app names for clarity:

<single-spa-router>
  <div class="root">
    <!-- Vue 2 apps -->
    <route path="a">
      <application loader="loader" name="@company/app-a"></application>
    </route>
    <route path="b">
      <application loader="loader" name="@company/app-b"></application>
    </route>

    <!-- Vue 3 apps (different JS, styles, config, etc.) -->
    <route path="c">
      <nav class="navbar">
        <application name="@company/v3-app-navbar"></application>
      </nav>
      <main class="content ynv-app">
        <div class="card-header-container-wrapper">
          <div class="card-header-container bg-background"></div>
        </div>
        <application name="@company/v3-app-c"></application>
      </main>
    </route>
    <route path="d">
      <nav class="navbar">
        <application name="@company/v3-app-navbar"></application>
      </nav>
      <main class="content ynv-app">
        <div class="card-header-container-wrapper">
          <div class="card-header-container bg-background"></div>
        </div>
        <application name="@company/v3-app-d"></application>
      </main>
    </route>
  </div>
</single-spa-router>

@joeldenning
Copy link
Member

The mixture of vue 2 and vue 3 applications does not change how single-spa or single-spa-layout function. If the @company/v3-app-navbar and <main class="content ynv-app"> should always be mounted, I suggest the following layout definition:

<single-spa-router>
  <div class="root">
    <!-- Vue 2 apps -->
    <route path="a">
      <application loader="loader" name="@company/app-a"></application>
    </route>
    <route path="b">
      <application loader="loader" name="@company/app-b"></application>
    </route>

    <!-- Vue 3 apps (different JS, styles, config, etc.) -->
    <nav class="navbar">
      <application name="@company/v3-app-navbar"></application>
    </nav>
    <main class="content ynv-app">
      <div class="card-header-container-wrapper">
        <div class="card-header-container bg-background"></div>
      </div>
      <route path="c">
        <application name="@company/v3-app-c"></application>
      </route>
      <route path="d">
        <application name="@company/v3-app-d"></application>
      </route>
    </main>
  </div>
</single-spa-router>

If the <nav> and <main> should not always be mounted (but rather only for c and d routes), then I'm open to a pull request fixing the problem.

@ddavid93
Copy link
Author

ddavid93 commented Feb 9, 2025

Hi @joeldenning I think we will need a new PR, because unfortunately @company/v3-app-navbar should not always be mounted, just in the case of being a Vue3 app.
So, that's was the reason of the first discussion about matching more than one route. Or even better as you said should be amazing that SingleSpa knows "dom diffing to avoid unnecessary unmounting/remounting in all situations".

Btw I have been also working in a WebComponent inspired by ImportMapOverrides, because in my current company, that have a new modern GUI and some features (still under development). If you can take a look I will appreciate it:

https://github.com/ddavid93/importmap-manager

So, how we proceed with the current "improve or fix" that we need in the single-spa routing?

@ddavid93
Copy link
Author

Hi @joeldenning , sorry can you take a look to my previous comment please?

@joeldenning
Copy link
Member

@company/v3-app-navbar should not always be mounted, just in the case of being a Vue3 app.

Could you clarify what you mean by this? The framework that a single-spa application uses is irrelevant to the layout definition.

@ddavid93
Copy link
Author

@joeldenning so, basically the point is that @company/v3-app-navbar will only be loaded for "c" or "d" routes (Not Always), because they are Vue3 projects. The rest of routes that we have use Vue2 and have another logic.

@joeldenning
Copy link
Member

I understand the issue now, so reopening. Pull requests accepted. I likely won't work on this soon because I have a lot of other priorities, unless you're willing to join singlespa patreon or make github sponsors donation.

@joeldenning joeldenning reopened this Feb 22, 2025
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

2 participants