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

Extract "css" variable in JS for custom Server Side Rendering #177

Open
bogas04 opened this issue Jun 21, 2019 · 4 comments
Open

Extract "css" variable in JS for custom Server Side Rendering #177

bogas04 opened this issue Jun 21, 2019 · 4 comments

Comments

@bogas04
Copy link

bogas04 commented Jun 21, 2019

Hi,

We use css-modules in a react app. In our webpack based web app, in order to collect styles for the critical components, we basically wrap those components into a HOC. This decorator basically accumulates all the generated CSS and inlines it into the HTML while server side rendering.

Example;

// component
import styles from "./styles.scss";
import { withStyles } from "utils/with-styles";

function Component (props) {
  return <div className={styles.wrapper}>{props.children}</div>;
}

export default withStyles(Component, styles);
// utils/with-styles.js
export function withStyles(Component, styles) {
  const context = useContext(CriticalCSSContext);
  
  if(context && context.addStyles) {
    if (!context.criticalStyles.has(styles._getId) {
      context.addStyles(styles._getId(), styles._getCss());
    }
  }

  return (props) => <Component {...props} />;
}
// index.js
function App() {
  const criticalStyles = useRef({});
  const criticalCss = {
    addStyles: (id, css) => {
      criticalStyles[id].current = css
    },
    criticalStyles: {
      has: (id) => criticalStyles.current[id],
    },
  };
  return (
    <CriticalCSSContext.Provider value={criticalCss}>
      <Root />
    </CriticalCSSContext.Provider>
  );
}

While we can access the className-generatedClassName map, we can't quite get the actual "css" content. To access the generated CSS, we wrote a custom style loader that exports the CSS content.

I was wondering if the same could be achieved by getting a onExport hook/plugin/loader where we can essentially add module.exports.css = css; line in the final css module file.

This is the style loader we wrote for webpack;

// custom_webpack_style_loader.js
var stringifyRequest = require("loader-utils").stringifyRequest;

module.exports = function loader() {};

module.exports.pitch = function pitch(remainingRequest) {
    if (this.cacheable) {
        this.cacheable();
    }

    return `
      var content = require(${stringifyRequest(this, `!!${remainingRequest}`)});
      if (typeof content === 'string') {
        content = [[module.id, content, '']];
      }
      module.exports = content.locals || {};
      module.exports._getContent = () => content;
      module.exports._getCss = () => content.toString();
      module.exports._getId = () => module.id;
  `;
};

I'm sorry if this is beyond the scope of this plugin, but would love some pointers to support this. Thank you so much for this wonderful plugin!

@IchabodDee
Copy link

IchabodDee commented Jul 12, 2019

@bogas04 Did you end up getting this to work? Or did you go a different direction on achieving SSR?

I'm working on a project that uses SCSS and it would be a big undertaking to switch to something that more easily supports SSR (Emotion). The injection behavior is leading to Flashes of Unstyled Content, and I was hoping to find another path. The extract option only gives one big bundle, which isn't helpful in sending critical css on the first request.

Exposing the actual css content sounds like a good plan, because then our web app will be able to pick up the styles from our components module that we are building with Rollupjs.

@bogas04
Copy link
Author

bogas04 commented Jul 15, 2019

@IchabodDee Nah couldn't get much time to go through plugin system of rollup/postcss. This is a great approach for css-modules, and the above solution would work really fine for webpack (swiggy.com uses it), just need to port it to rollup.

@bogas04
Copy link
Author

bogas04 commented Aug 14, 2019

@IchabodDee I ended up making a plugin for this. Man rollup is so simple.

This should work for above code snippet.

/**
 * Simple rollup plugin to inject `_getCss` and `_getId` functions to default export of scss files.
 */
function injectStyleFunctions() {
    const injectFunctions = code => code.replace(
        "export default {",
        "export default {".concat([
            "_getCss: function() { return css; },",
            `_getId: function() { return "${id}"; },`,
         ].join("")
   ));

    return {
        name: "injectStyleFunctions",
        async transform(code, id) {
            if (id.includes(".scss")) {
                return {
                    id,
                    code: injectFunctions(code)
                };
            }
            return null;
        },
    };
}

This was referenced Mar 8, 2020
@abhinavpreetu
Copy link

@bogas04 @IchabodDee Hey folks, we also had a similar use case, where we want to get hold of the CSS to implement SSR. We found that we can get hold of the CSS as the named export. So in the above example, you can try:

- import styles from "./styles.css"
+ import styles, { stylesheet } from "./styles.css"

The stylesheet variable holds the stringified CSS.

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

3 participants