0

I would like to properly load css and icons into my web component, right now styles are loaded but icons are not. Here is my web component:

import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App.js";

import preactbase from "primereact/resources/primereact.css";
import preacttheme from "primereact/resources/themes/lara-light-blue/theme.css";
import picons from "primeicons/primeicons.css";

class Dumb extends HTMLElement {
  constructor() {
    super();
    console.log("HEY");
    this._shadowRoot = this.attachShadow({ mode: "open" });
    var style = document.createElement("style");
    style.textContent = preactbase + preacttheme + picons;
    var mountPoint = document.createElement("div");
    this._shadowRoot.appendChild(style);
    this._shadowRoot.appendChild(mountPoint);
    var reactroot = createRoot(mountPoint);
    reactroot.render(<App />);
  }

  connectedCallback() {}
}

window.customElements.define("dumb-app", Dumb);

As you can see, I am trying to wrap a minimal react spa which uses primeicons. App.js file is straightforward:

import "primeicons/primeicons.css";
import { Button } from "primereact/button";

function App() {
  return (
    <div>
      <h1>Hello from React!</h1>
      <i className="pi pi-check"></i>
      <br></br>
      <Button label="webcomponent" icon="pi pi-check" />
    </div>
  );
}

export default App;

I am using webpack with the following configuration:

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const pkg = require("./package.json");
const commonPaths = require("./build-utils/config/commonPaths.js");

const isDebug = !process.argv.includes("release");
const port = process.env.PORT || 3000;

module.exports = {
  entry: commonPaths.entryPath,
  output: {
    uniqueName: pkg.name,
    publicPath: "/",
    path: commonPaths.outputPath,
    filename: `${pkg.version}/js/[name].[chunkhash:8].js`,
    chunkFilename: `${pkg.version}/js/[name].[chunkhash:8].js`,
    assetModuleFilename: isDebug ? `assets/[path][name].[contenthash:8][ext]` : `assets/[name].[contenthash:8][ext]`,
    crossOriginLoading: "anonymous",
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "public/index.html",
      filename: "index.html",
    }),
  ],
  devServer: {
    port,
    static: {
      directory: commonPaths.outputPath,
    },
    historyApiFallback: {
      index: "index.html",
    },
    webSocketServer: false,
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ["babel-loader"],
      },
      {
        // Important: css-loader first (resolves url()), then to-string-loader
        test: /\.css$/i,
        use: [
          {
            loader: "to-string-loader",
          },
          {
            loader: "css-loader",
            options: { url: true },
          },
        ],
      },
      {
        // Inline modern webfonts into CSS (no network requests)
        test: /\.(woff|woff2)$/i,
        type: "asset/inline",
      },
      {
        // Other assets (images, etc.)
        test: /\.(png|jpg|jpeg|gif|svg|eot|ttf)$/i,
        type: "asset/resource",
        generator: {
          filename: "assets/[name].[hash][ext]",
        },
      },
    ],
  },
  resolve: {
    extensions: ["*", ".js", ".jsx"],
  },
};

And this is the package.json:

{
  "name": "y",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "start": "webpack serve --mode development"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@babel/preset-react": "^7.27.1",
    "primeicons": "^7.0.0",
    "primereact": "^10.9.7",
    "react": "^19.1.1",
    "react-dom": "^19.1.1",
    "to-string-loader": "^1.2.0"
  },
  "devDependencies": {
    "@babel/core": "^7.28.3",
    "@babel/preset-env": "^7.28.3",
    "babel-loader": "^10.0.0",
    "css-loader": "^7.1.2",
    "html-webpack-plugin": "^5.6.4",
    "raw-loader": "^4.0.2",
    "style-loader": "^4.0.0",
    "webpack": "^5.101.3",
    "webpack-cli": "^6.0.1",
    "webpack-dev-server": "^5.2.2"
  }
}

I can see what I am missing. I can see the <style> element filled with, I guess, all the styles I import, but no icon is redered. Full project can be fount here, thank you!

1 Answer 1

0

Here is what I did to get things done.

I didn't load properly all the .css/.scss files. Style elements must use textContent to be filled with css/scss contents:

    import mycss from "./css/my.css";
    ...
    const shadow = this.attachShadow({ mode: "open" });
    ...
    const stylecss = document.createElement("style");
    stylecss.textContent = mycss;
    shadow.appendChild(stylecss);

Configure webpack.config.js to load .css/.scss files properly. Here is a snippet:

    {
      test: /\.css$/,
      loader: "css-loader",
      options: {
        esModule: false,
        exportType: "string",
        url: true,
      },
      sideEffects: true,
    },
    {
      test: /\.(scss|sass)$/,
      use: [
        { loader: "to-string-loader" },
        { loader: "css-loader" },
        { loader: "sass-loader" },
      ],
      sideEffects: true,
    },

Add primeicons into light DOM to let fonts be used in shadow DOM:

    import reacticons from "primeicons/primeicons.css";
    ...
    const globalStyle = document.createElement("style");
    globalStyle.textContent = reacticons;
    document.head.appendChild(globalStyle);

Check if style is not already present in light to avoid attach it twice.

Prevent dialogs or other element from being added to the light DOM: primereact attach dialogs to document.body by default so they won't receive styles loaded into shadow DOM. As primereact docs suggests, I did this to prevent that behavior:

    ...
    const configs = {
      appendTo: "self",
    };

    reactroot.render(
      <PrimeReactProvider value={configs}>
        <App messageboxes={messageboxes} />
      </PrimeReactProvider>,
    );
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.