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

Allow customization of prerender DOM #253

Open
yepitschunked opened this issue Mar 16, 2023 · 1 comment
Open

Allow customization of prerender DOM #253

yepitschunked opened this issue Mar 16, 2023 · 1 comment

Comments

@yepitschunked
Copy link

Happo has a stylesheets config option to ensure styles are applied at render time. We'd like these styles to also be injected at prerender time. This allows components such as react-autosize-textarea, which call window.getComputedStyle while rendering, to behave more realistically.

Furthermore, we've run into some execution ordering bugs in JSDOM which result in the <script> tags after a stylesheet <link> tag executing before the stylesheet is loaded. This should not happen in a real browser, since styles ought to be parser-blocking. Our solution was to inline the stylesheet contents in a <style> tag.

We were able to work around all of these problems by using the plugins API, which allows providing a custom DomProvider. We extended the JSDOMDomProvider and modified this.dom.

Implementation ideas

Prerender currently operates on a static JSDOM template:

this.dom = new JSDOM(
`
<!DOCTYPE html>
<html>
<head>
<script src='file://${webpackBundle}'></script>
</head>
<body>
</body>
</html>
`.trim(),

  • A very basic solution would be to just inject the stylesheets param in here as a tag, the same way that the render environment works. However, this won't support the JSDOM workaround.
  • Another option would be to automatically inline the styles. However, users might want to link to an external stylesheet that isn't on the filesystem, which would require some work to fetch the stylesheet.
  • A third option would be to allow customizing the JSDOM template in addition to the existing jsdomOptions.
  • Lastly, we could just document the fact that DomProviders exist and can be overridden by a plugin. We could use our custom DomProvider as an example:
module.exports = function happoPrerenderWithStylesheetsPlugin(
  stylesheetAbsolutePaths,
  // the regular jsdomOptions from the config aren't passed to a plugin DOMProvider, maybe we can fix this
  jsdomOptions,
) {
  class JSDOMProviderWithStylesheets extends HappoJSDOMProvider {
    constructor(jsdomOpts, providerOptions) {
      super(jsdomOpts, providerOptions);
      // this.dom was created in the superclass
      const {
        window: { document },
      } = this.dom;

      stylesheetAbsolutePaths.forEach((href) => {
        const cssSource = fs.readFileSync(href, 'utf-8');
        const style = document.createElement('style');
        style.textContent = cssSource;

        document.head.prepend(style);
      });
    }
  }

  return {
    DomProvider: JSDOMProviderWithStylesheets.bind(JSDOMProviderWithStylesheets, jsdomOptions),
  };
};
@trotzig
Copy link
Contributor

trotzig commented Mar 17, 2023

Hi @yepitschunked! Thank you for that detailed description, and for providing a workaround option through the DomProvider plugin option.

The alternatives you listed to solve this all seem doable and fair. I think I like the customizing JSDOM html template the most. We could allow an async function return an html string that we use as the template, and then you could fully customize it.

I'd also like to list a few alternative options that you could explore:

  • By setting prerender: false you'll skip the JSDOM pre-rendering phase which could resolve issues.
  • You can switch to using a Static bundle. It opens up for even more customization of the html template, the javascript bundle, etc.

One action item I'm recording from this is that we need to document the DomProvider option for plugins. I think I'll add a "Custom plugin" to the Plugins page.

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