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

@emotion/cache browser specific module breaks SSR #1246

Closed
GU5TAF opened this issue Feb 27, 2019 · 23 comments
Closed

@emotion/cache browser specific module breaks SSR #1246

GU5TAF opened this issue Feb 27, 2019 · 23 comments

Comments

@GU5TAF
Copy link

GU5TAF commented Feb 27, 2019

  • emotion version: 10.0.7
  • react version: 16.8.0

Relevant code:
https://github.com/emotion-js/emotion/blob/master/packages/cache/src/index.js#L69

What you did:
Tried to SSR using static-site-generator-webpack-plugin.

What happened:

ERROR in ReferenceError: document is not defined at createCache
(webpack:////node_modules/@emotion/cache/dist/cache.browser.esm.js?:109:38)

Problem description:
When @emotion/cache gets imported by webpack with target: web it fails due to the browser specific module not including the check for typeof document !== 'undefined'.
As mentioned in #1113 this is a known issue and setting target: node is not an adequate solution.

Suggested solution:
Drop the browser specific version of the module.

@GU5TAF
Copy link
Author

GU5TAF commented Feb 27, 2019

The root cause of this is an optimisation done in the module bundler preconstruct that replaces "typeof document" with "object" and is then removed by a minifier that does dead code removal. This is definitely the intended behaviour but perhaps requires some rethinking as its intention probably wasn't to make it impossible to use static-site-generator-webpack-plugin amongst others.

@Andarist
Copy link
Member

As mentioned in #1113 this is a known issue and setting target: node is not an adequate solution.

Isnt it? Could you prepare a simple repository reproducing the issue with your setup? I could take a look then.

@GU5TAF
Copy link
Author

GU5TAF commented Feb 27, 2019

Could you prepare a simple repository reproducing the issue with your setup? I could take a look then.

Absolutely, here it is https://github.com/GU5TAF/emotion-cache-ssr-error

Just yarn && yarn start and you should be good to go.
Check the console in the browser and you'll find Uncaught ReferenceError: require is not defined because we set the target to node but are now running the bundle in a browser.

@rdadoune
Copy link

I had the same problem, I wrote a quick monkey patch to get my build working. This script can be used post install or pre webpack. It basically unsets the "browser" mapping in the package.json of every @emotion package.

const fs = require('fs');
const { sync } = require('glob');

sync('./node_modules/@emotion/*/package.json').forEach(src => {
  const package = JSON.parse(fs.readFileSync(src, 'utf-8'));
  const browser = package.browser;
  delete package.browser;
  if (browser) {
    package._browser = browser;
  }
  fs.writeFileSync(src, JSON.stringify(package, null, 2))
});

@Andarist
Copy link
Member

@GU5TAF it's the problem of your setup. I'm not sure how static-site-generator-webpack-plugin should be used, so I can't quite tell you how you should fix it.

The problem (the require call) is coming from react-dom/server which you try to ship to the browser by including it in your entry point with import statement.

You can check out that this is not emotion-related by removing all emotion stuff from your demo - it still won't work.

@GU5TAF
Copy link
Author

GU5TAF commented Feb 28, 2019

The problem (the require call) is coming from react-dom/server which you try to ship to the browser by including it in your entry point with import statement.

My apologies @Andarist, there was indeed a typo in my call to hydrate but apart from that I can assure you that setting the target to web and dropping emotion it'll work just fine.

I created a new branch called working-example where I do just that so you can take it for a spin.

@Andarist
Copy link
Member

But this is again caused by how your entry is structured. Your entry gets shipped to the browser and you include in it react-dom/server (which is wrong, it won't be used in the browser at all but you ship the whole server renderer nevertheless). The problem is that u want to handle both server & client paths through a single webpack config - it can't work, at least not with a structure which you have there right now.

@GU5TAF
Copy link
Author

GU5TAF commented Feb 28, 2019

it can't work, at least not with a structure which you have there right now.

It can't work with emotion or it can't work in general?
As I said, there is now a branch that is fully working, static render + hydrate in the browser.

You're correct that react-dom/server will be shipped to the browser unnecessarily, however, react-dom include a browser version of react-dom/server for that very reason. When bundled with --mode=production, react-dom comes in at 105.51 KB and react-dom/server comes in at 18.75 KB which is only 7 KB gzipped. An unnecessary 7 KB for sure but not the end of the world in my opinion.

@rdadoune
Copy link

The problem with the environment I was using is that it's not for the browser or for node, the script needs to be run ad-hoc via Google V8 JS engine.

@GU5TAF
Copy link
Author

GU5TAF commented Feb 28, 2019

@rdadoune that sounds like a challenging environment to develop for 😅thanks for the monkey patch!

@Andarist I agree that it would be even more optimal for the resultant bundle to run webpack twice, once to generate the html, storing only html files and then once more to create the browser bundle.

However, this approach with a bundle that can run in both contexts isn't that uncommon and all it would take to support it completely is for emotion not to over optimize the browser bundle.

The monkey patch @rdadoune provided us with fixes the issue, once run the current broken example in master just works. Surely this is desirable behaviour?

I've applied it as a pre build step in a new branch called rdadoune if you're curious.

@ahutchings
Copy link
Contributor

I'm seeing the same error when using html-webpack-plugin with a custom template that does React SSR. Setting the webpack target to node is not an option with this plugin since the HTML & JS are generated using the same webpack config.

Conversely, and maybe this should be tracked as a separate issue - when doing SSR in node, with a mocked document object (JSDOM), @emotion/cache does not populate the cache with inserted rules, which prevents extraction of critical styles. This appears to be due to the isBrowser check resolving to true. Mocking document (and window) is necessary when using 3rd-party React components which assume they are running in a browser and access those browser globals.

@Andarist
Copy link
Member

Andarist commented Nov 3, 2019

From what I know - most SSR solutions use different webpack settings for server and client builds and this is how this problem should ultimately be solved. If any particular SSR-solution doesn't differentiate this, it really should - so I would advise reporting this to those SSR-solutions as a thing they should handle.

@johot
Copy link

johot commented Mar 19, 2020

Thank you for this thread!

This was actually a big problem for us! We wanted to use Emotion 10 together with ReactJS.NET for using React components inside Razor pages in ASP.NET. The problem here is we can't use target "node" because the V8 engine it is running can't understand it (no support for require etc). So we need to use target: "web" but still enable SSR.

By manually deleting all the browser: settings from each emotion package.json like @rdadoune suggested we got it working!

So there are definately cases where a check should be made even if it is the browser specific module.

Follow up findings:

I could also make it work without the monkey patch by setting (in webpack.config.js):

 resolve: {
      aliasFields: ["module"]
},

@navgarcha
Copy link

@johot great find, thanks for the follow up.

@valentinradu
Copy link

@johot Thank you! I've spent more time than I'd like to admit on this. Not sure what it does, since the docs are not that clear about it, but for now I'm just happy it works! Thanks again!

@el-ethan
Copy link

I am having a similar issue with @emotion/react@11.1.2. I am working on a component library created using create-react-library, and using @emotion/react for styling. It works fine in many contexts, but when we try to use components from this library in a Next.js app that uses SSR, we get a similar error to the one mentioned above:

ReferenceError: window is not defined
    at Object.<anonymous> (.../node_modules/@emotion/react/dist/emotion-react.browser.esm.js:310:37)

which I believe has a similar cause, since our compiled component library has the following checks throughout (from Emotion):

var isBrowser = "object" !== 'undefined';

which leads the code down paths where things like window and document are expected to exist, which they won't in the SSR case. I am able to work around this issue by importing the components in a try/catch, or by using Next.js's dynamic imports, but it would be great if there was a solution that did not require workarounds. I am not sure if the issue in our case is related to some configuration (to microbundle, webpack, babel, typescript, etc.), or whether it is a genuine bug. Unfortunately, because of the nature of the project, I don't have access to most of those configs, and since it is a private repository, I don't have an example repo on hand to share, so I apologize for that. My understanding is that Emotion v11 with Next.js SSR "just works", but perhaps that is only if I am using Emotion directly in a Next.js app, as opposed to my case where I am using Emotion in a standalone component library, and then installing that library as a dependency of a Next.js app. Do you have any suggestions as to how to move forward?

@Andarist
Copy link
Member

@el-ethan it looks like your bundler is configured to consume browser files - so it's not a surprise that this breaks in SSR. In webpack when bundling for SSR you should configure target: 'node' and this is what most of the webpack-based tools (like Next.js) do. If those files have been loaded by Next's SSR then there is surely something configured wrongly. If you share a runnable repro case I could take a look at your problem.

@hasparus
Copy link
Contributor

hasparus commented Mar 20, 2021

I had the same problem with Gatsby in Theme UI docs. (Worked around it already.)

it looks like your bundler is configured to consume browser files - so it's not a surprise that this breaks in SSR.

Is this still true? — Node 15 consumes esmodules, and the window will still be undefined there, and I think that even in previous version people and metaframeworks do configure bundlers to consume esm.

@Andarist
Copy link
Member

Esm files !== browser files. The comment was referring to bundler using package.json#browser even though the target environment was node

@hasparus
Copy link
Contributor

hasparus commented Mar 20, 2021

Oh, sorry. I missed that emotion-react.esm.js exists and has var isBrowser = typeof document !== 'undefined'; instead of isBrowser = "object" !== 'undefined';. Sorry for necroposting. Definitely a fault on bundler's config side.

@breno-sapucaia
Copy link

I am having a similar issue with @emotion/react@11.1.2. I am working on a component library created using create-react-library, and using @emotion/react for styling. It works fine in many contexts, but when we try to use components from this library in a Next.js app that uses SSR, we get a similar error to the one mentioned above:

ReferenceError: window is not defined
    at Object.<anonymous> (.../node_modules/@emotion/react/dist/emotion-react.browser.esm.js:310:37)

which I believe has a similar cause, since our compiled component library has the following checks throughout (from Emotion):

var isBrowser = "object" !== 'undefined';

which leads the code down paths where things like window and document are expected to exist, which they won't in the SSR case. I am able to work around this issue by importing the components in a try/catch, or by using Next.js's dynamic imports, but it would be great if there was a solution that did not require workarounds. I am not sure if the issue in our case is related to some configuration (to microbundle, webpack, babel, typescript, etc.), or whether it is a genuine bug. Unfortunately, because of the nature of the project, I don't have access to most of those configs, and since it is a private repository, I don't have an example repo on hand to share, so I apologize for that. My understanding is that Emotion v11 with Next.js SSR "just works", but perhaps that is only if I am using Emotion directly in a Next.js app, as opposed to my case where I am using Emotion in a standalone component library, and then installing that library as a dependency of a Next.js app. Do you have any suggestions as to how to move forward?

can you share the solution with an example ?

@el-ethan
Copy link

el-ethan commented Jul 6, 2022

@breno-sapucaia sorry, I wish I would have included an example of the workaround at the time, because I don't recall exactly what we did now, and I no longer have access to that code base. This example shows one thing we tried that worked, but we didn't like having to use that work around. Ultimately, we ended up going moving to styled-components for various reasons that I don't recall. Wish I could be of more help, but I'm afraid I never really came to a good understanding of the problem or a good solution.

@popuguytheparrot
Copy link

so how to fix it? Now in mui 5 with next i got this error

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