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

PROPOSAL: Add the posibility of recycle micro-apps styles, after those are unmounted #1209

Open
Jorgitoa9507 opened this issue Mar 28, 2024 · 1 comment

Comments

@Jorgitoa9507
Copy link

Jorgitoa9507 commented Mar 28, 2024

Problem Statement

In a single-spa environment, where multiple micro-apps coexist within the same application, managing the styles of each micro-app can be challenging. The default behavior of Webpack's style-loader plugin is to append a <style> tag to the document head when the micro-app is loaded. However, this tag is not removed when the micro-app is unmounted, leading to potential conflicts and unintended styling effects across the application.

Proposed Solution

To address this issue, we propose the following solution:

Root App Event Handling:

Implement a built-in event handler in single-spa that listens for the single-spa:before-routing-event.
This event handler should remove and store the <style> tag when a micro-app is unmounted, using a Map to maintain a cloned copy of the styles.
When a micro-app is mounted, the event handler should restore the previously stored styles by appending the cloned <style> tag to the document head.

Example implementation:

const MODIFIED_STYLES = new Map();

function recycleMarkedStyleElements(evt) {
  const { NOT_MOUNTED, MOUNTED } = evt.detail.appsByNewStatus;

  NOT_MOUNTED.forEach((app) => {
    const appStyle = document.head.querySelector(`style[id='recycle${app}']`);
    if (!appStyle) return;
    try {
      MODIFIED_STYLES.set(app, appStyle.cloneNode(true));
      document.head.removeChild(appStyle);
    } catch {
      // Handle potential errors
    }
  });

  MOUNTED.forEach((app) => {
    const appStyle = document.head.querySelector(`style[id='recycle${app}']`);
    if (!appStyle && MODIFIED_STYLES.get(app)) {
      document.head.appendChild(MODIFIED_STYLES.get(app));
      MODIFIED_STYLES.delete(app);
    }
  });
}

window.addEventListener('single-spa:before-routing-event', recycleMarkedStyleElements);

Webpack Configuration:

To make this work, the developer should change the configuration option for the style-loader that allows to generate a unique ID for a unique <style> tag, tied to the micro-app's name.
Example configuration:

test: /\.css$/,
use: [
  {
    loader: "style-loader",
    options: {
      attributes: {
        id: "recycle@app-name"
      },
      injectType: "singletonStyleTag"
    }
  },
  // Rest of CSS plugins
]

Integration with single-spa:

Integrate this functionality as a built-in feature or plugin for single-spa, allowing developers to easily configure the desired behavior for their micro-apps' styles.
Provide a centralized approach to managing micro-app styles, reducing the need for individual implementations across projects.

Benefits

Implementing this solution within single-spa would provide the following benefits:

Style Isolation: Micro-app styles would be effectively isolated, preventing conflicts and unintended styling effects across the application.
Centralized Management: A centralized approach to managing micro-app styles would reduce the need for individual implementations across projects.
Configurable: Developers would have the flexibility to choose which micro-apps should have their styles recycled, based on their specific requirements.

Considerations

Performance Impact:
The proposed solution involves cloning and appending/removing <style> tags on every routing event, which may have potential performance implications.
It is recommended to conduct performance tests and monitoring to determine the actual impact on specific use cases.

@MilanKovacic
Copy link
Collaborator

Hi, there is definitely work to do in simplifying the asset handling, scoping, etc.
However, in my opinion, putting this behavior in the single-spa library might not be the right place.
Microfrontends should clean up the resources in the unmount lifecycle hook. Potentially, it could be offered as some kind of a "plugin" in a lifecycle helper.

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