webpack-contrib / style-loader

Style Loader
MIT License
1.65k stars 473 forks source link

Styles not injected in multiple iframes using style-loader with lazyStyleTag #631

Closed shriyajuneja closed 1 month ago

shriyajuneja commented 3 months ago

Bug report

When using the lazyStyleTag option, and a component (e.g., Button) uses styles.use({ target: document.head }), and is used in multiple iframes, The styles are only applied to the button in the first iframe on the page. Iframes are created using react-frame-component.

Actual Behavior

The styles are only applied to the button/buttons in the first iframe on the page.

Expected Behavior

The expected behavior is for styles to be applied to buttons in all iframes.

How Do We Reproduce?

https://codesandbox.io/p/live/108b23dd-8fa2-4dee-be00-9d7f599aecde

Please paste the results of npx webpack-cli info here, and mention other relevant information

System: OS: macOS 14.3.1 CPU: (12) arm64 Apple M2 Max Memory: 10.21 GB / 64.00 GB Binaries: Node: 21.6.1 - /opt/homebrew/bin/node Yarn: 1.22.21 - /opt/homebrew/bin/yarn npm: 10.2.4 - /opt/homebrew/bin/npm Browsers: Edge: 125.0.2535.51 Safari: 17.3.1

alexander-akait commented 3 months ago

Please create reproducible example using github, I can't open your link

shriyajuneja commented 3 months ago

@alexander-akait please check https://shriyajuneja.github.io/lazy-style-loader/ code- https://github.com/shriyajuneja/lazy-style-loader

alexander-akait commented 1 month ago

Sorry for delay, I see your problem, it is a limitation, because we need to track how many times styles were used to properly unmount and avoid duplication them and your runtime code into global document, so logic prevents to inject styles twice

alexander-akait commented 1 month ago

My recommendation to solve such problem:

Let's avoid using style-loader for lazy components inside iframe and return CSS as just string, i.e.

{
          test: /\.module\.scss$/,
          use: [
            {
              loader: "css-loader",
              options: {
                // Here the option
                exportType: "string",
                modules: {
                  localIdentName: "[name]_[local]_[hash:base64:5]", // Customize the format of generated class names
                },
              },
            },
            "sass-loader",
          ],
},

And use this logic inside component:

import React, { useLayoutEffect } from "react";
import { useFrame } from "react-frame-component";

import style, { button } from "./Button.module.scss";

const Button = () => {
  const { document } = useFrame();

  useLayoutEffect(() => {
    var styleElement = document.createElement('style');
    styleElement.textContent = style;
    document.head.appendChild(styleElement);
    return () => {
      document.head.removeChild(styleElement)
    };
  }, []);
  return <button className={button}>Submit</button>;
};

export default Button;

To avoid code duplication you can create own hook or just create a small helper function

Feel free to feedback