rollup-plugin-postcss icon indicating copy to clipboard operation
rollup-plugin-postcss copied to clipboard

React npm packages using rollup not working with srr or nextjs?

Open siamahnaf opened this issue 3 years ago • 5 comments

I create a simple react packages for personal uses (private package). But when I want to use it into nextjs project it's css not working with ssr. Actually, working but not ssr friendly. Here after component loading then css will be loaded. But I want it with ssr. I mean css also work with ssr.

rollup.config.mjs-

import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import dts from "rollup-plugin-dts";
import postcss from "rollup-plugin-postcss";
import peerDepsExternal from "rollup-plugin-peer-deps-external";

export default [
    {
        input: "src/index.ts",
        output: [
            {
                file: "dist/cjs/index.js",
                format: "cjs",
                sourcemap: true,
            },
            {
                file: "dist/esm/index.js",
                format: "esm",
                sourcemap: true,
            },
        ],
        plugins: [
            peerDepsExternal(),
            resolve(),
            commonjs(),
            typescript({ tsconfig: "./tsconfig.json" }),
            postcss()
        ],
        external: ["react", "react-dom"]
    },
    {
        input: "dist/esm/types/index.d.ts",
        output: [{ file: "dist/index.d.ts", format: "esm" }],
        plugins: [dts()],
        external: [/\.css$/]
    }
];

Can anyone help me.

siamahnaf avatar Dec 19 '22 14:12 siamahnaf

@siamahnaf I have found a solution.

In the library:

// src/style-inject.js
export default function styleInject(css, id, { insertAt } = {}) {
  if (!css) return
  if (typeof document === 'undefined') {
    globalThis.ssrCss = globalThis.ssrCss || []
    globalThis.ssrCss.push({ css, id })

    return
  }

  if (document.getElementById(id)) return

  const head = document.head || document.getElementsByTagName('head')[0]
  const style = document.createElement('style')
  style.id = id
  style.type = 'text/css'

  if (insertAt === 'top') {
    if (head.firstChild) {
      head.insertBefore(style, head.firstChild)
    } else {
      head.appendChild(style)
    }
  } else {
    head.appendChild(style)
  }

  if (style.styleSheet) {
    style.styleSheet.cssText = css
  } else {
    style.appendChild(document.createTextNode(css))
  }
}

// rollup.config.js
import { randomUUID } from 'crypto';
import path from 'path';

const styleInjectPath = path
  .resolve('./src/style-inject.js')
  .replace(/[\\/]+/g, '/')

const ids = new Map()
const getUniqueId = (id) => {
  if (ids.has(id)) return ids.get(id)
  const uid = randomUUID()
  ids.set(id, uid)

  return uid
}

...
postcss({
      inject(cssVariableName, id) {
        return `
          import styleInject from '${styleInjectPath}';
          styleInject(${cssVariableName}, 'style-${getUniqueId(id)}');
        `
      },
    }),
...

In the Next js project

// next pages/_document.js
import React from 'react'

type SSRCssModule = {
  css: string
  id: string
}
interface GlobalThis {
  ssrCss?: SSRCssModule[]
}

declare const globalThis: GlobalThis

const SSRInjectStyles: React.FC = () => {
  if (!globalThis.ssrCss) return null

  return (
    <>
      {globalThis.ssrCss.map((module: { css: string; id: string }) => (
        <style
          dangerouslySetInnerHTML={{
            __html: module.css,
          }}
          id={module.id}
          key={module.id}></style>
      ))}
    </>
  )
}


export default Document(props) {
  return (
    ...
  <Head>
  ...
  <SSRInjectStyles />
  </Head>
    ...
  )
}

alfredosalzillo avatar May 07 '23 18:05 alfredosalzillo

where is globalThis coming from @alfredosalzillo in the first file it is not defined

adarsh-drishya avatar Sep 21 '23 03:09 adarsh-drishya

your solution works @alfredosalzillo. I put the SSRInjectStyles component within my own library to allow consumers to import it.

and I found that the styles were inserted multiple times. I had to change a little bit your styleInject script by next one.

export default function styleInject(css, id, { insertAt } = {}) {
	if (!css) return;
	if (typeof document === "undefined") {
		globalThis.ssrCss = globalThis.ssrCss || [];
		const keys = globalThis.ssrCss.reduce((acc, curr)=> {
			return {
				...acc,
				[curr.id]: {
					...curr
				}
			};
		},{});
		if(!keys[id]){
			globalThis.ssrCss.push({ css, id });
		}
		return;
	}

	if (document.getElementById(id)) return;

	const head = document.head || document.getElementsByTagName("head")[0];
	const style = document.createElement("style");
	style.id = id;
	style.type = "text/css";

	if (insertAt === "top") {
		if (head.firstChild) {
			head.insertBefore(style, head.firstChild);
		} else {
			head.appendChild(style);
		}
	} else {
		head.appendChild(style);
	}

	if (style.styleSheet) {
		style.styleSheet.cssText = css;
	} else {
		style.appendChild(document.createTextNode(css));
	}
}

cristiannietodev91 avatar Sep 29 '23 00:09 cristiannietodev91

@adarsh-drishya wich bundler are you using?

alfredosalzillo avatar Sep 29 '23 06:09 alfredosalzillo

@cristiannietodev91 the bundler and the import cache of modules should have prevented the duplication when a component is used multiple time during the SSR, but nice catch :)

alfredosalzillo avatar Sep 29 '23 06:09 alfredosalzillo