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

4.0 - [SSR] ServerStyleSheet's collectStyles fails to collect styles from symlinked modules #2322

Closed
damassi opened this issue Jan 14, 2019 · 30 comments

Comments

@damassi
Copy link

damassi commented Jan 14, 2019

I've recently begun a styled-components 4.0 upgrade on our apps but ran into a hard blocker around SSR, local development and symlinked modules using yarn link, which differs from v3.

Here's a repo reproducing the issue: https://github.com/damassi/styled-components-ssr-issue

Scenario:
Our main website is a complex host environment (shell) that imports React components (Apps) from other NPM modules. When the host receives a route request it performs an SSR render

const html = renderToString(sheet.collectStyles(<App />)
... 
res.send(html) 

and sends a payload down to the client, which is then rehydrated on mount.

When working from a local dev environment each of these modules is symlinked via yarn link so dependency changes don't need to be published to npm after each change.

Because of v4's new static context API, its no longer possible to yarn link submodules and preserve SSR rendering locally as different styled-component modules are now managing their own list of styled-components to render. From the host app it appears as though there's nothing to extract because no styled components have been created in that scope.

Due to application complexity, it's fairly common to run into SSR reconciliation issues and so disabling SSR during local development is not an option. Is there a way to work around? This appears to be a regression from v3 since symlinked modules + SSR behaved well.

(Note that the --preserve-symlinks suggestion from this thread doesn't seem to work, and we're not working from a monorepo like Lerna.)

Update:

Was able to resolve via require-control; see this message below.

@damassi damassi changed the title 4.0 - ServerStyleSheet's collectStyles fails to collect styles from symlinked modules 4.0 - [SSR] ServerStyleSheet's collectStyles fails to collect styles from symlinked modules Jan 14, 2019
@mxstbr
Copy link
Member

mxstbr commented Jan 14, 2019

You need to deduplicate your node_modules so there's only one instance of styled-components in your app. This is related to the new React context API, not any styled-components changes—all consumers of a context need to be created from the exact same code!

See the FAQ on the website for instructions with various build tools: https://www.styled-components.com/docs/faqs#why-am-i-getting-a-warning-about-several-instances-of-module-on-the-page

Hope that helps!

@mxstbr mxstbr closed this as completed Jan 14, 2019
@damassi
Copy link
Author

damassi commented Jan 14, 2019

@mxstbr, I could be misunderstanding, but I've read that FAQ a few times prior to posting here and it doesn't cover the issue during the SSR pass, since it doesn't involve Webpack or anything client-side / build-tool related, and additionally, ya'll have a large banner:

screen shot 2019-01-14 at 8 26 40 am

Which is exactly the problem that we're facing and the issue describes. Deduping doesn't work with yarn link or npm link and the symbolic link resolves to the node_modules folder of the linked module.

There is a --hoist command in Lerna, but we're not using a monorepo.

If this is a hard limitation around yarn link should we update the docs? Or perhaps add a hook for dynamically creating a styled-components context that can be shared / replaced?

All that said, if there's nothing we can do then there's nothing we can do 👍

@damassi
Copy link
Author

damassi commented Jan 16, 2019

It looks like we're going to have to refactor our apps and their dependencies under a yarn workspace to get around this limitation.

@mxstbr
Copy link
Member

mxstbr commented Jan 16, 2019

In the OP you said:

that imports React components (Apps) from other NPM modules

Aren't your React component libraries built with webpack or rollup? How are they created?

@damassi
Copy link
Author

damassi commented Jan 16, 2019

We use babel to compile out our sources into a dist folder.

@mxstbr
Copy link
Member

mxstbr commented Jan 16, 2019

Can you try marking styled-components as a peerDependency in your component lib? I think that should mean npm only installs one copy in the main app itself.

That makes development impossible (😅) but let's figure that out afterwards if it works.

@damassi
Copy link
Author

damassi commented Jan 16, 2019

That's how we currently have it (this is SC 3, from master):
https://github.com/artsy/palette/blob/master/package.json#L46

Which is then read into our other component library:
https://github.com/artsy/reaction/blob/master/package.json#L10

Which is then merged all together into our app server:
https://github.com/artsy/force/blob/master/package.json#L208

In the top two instances we install it as a devDep for local development, but yeah, let's see what happens... one sec.

@damassi
Copy link
Author

damassi commented Jan 16, 2019

Yup, that works since the only instance of styled-components is in the main app folder.

I guess a somewhat flimsy solution would be to write a script that deletes the folder from target locations, but yeah 😄

@mxstbr
Copy link
Member

mxstbr commented Jan 16, 2019

Yay! So now the question is how to fix development 😅

@damassi
Copy link
Author

damassi commented Feb 13, 2019

@mxstbr - Should this issue be reopened, or are there docs that could be added to better communicate the issue? For non-trivial applications moving to a workspace can be fraught with problems.

@quantizor
Copy link
Contributor

Well it's not an issue with the library, more an environmental restriction. For that reason I would not reopen this issue. If you feel there's a deficiency in our docs though, I'd welcome a PR there.

@knieber
Copy link

knieber commented Mar 5, 2019

@damassi We've just started running into the same situation with our project. It works fine when compiling with babel and then adding styled-components as a peerDependency and pulling down the published code from npm, however it isn't working when linking. I even looked at your component library repo, and it appears we're doing things very similarly. Did you all find a way to get this working with local development? Did using a yarn workspace work?

@quantizor
Copy link
Contributor

quantizor commented Mar 5, 2019 via email

@damassi
Copy link
Author

damassi commented Mar 5, 2019

@knieber - A yarn workspace is a very tricky thing to bolt on to an already complex app; we weren't able to get it working, and it seemed overly complex compared to our old workflow, including a project restructure (or scripts to clone). Ended up abandoning that idea after a few things broke.

Our work around was to setup this command in our main host app, which is a band-aid:

$ main-app/
$ main-app/ cd ../some-dep
$ some-dep/ rm -rf node_modules/styled-components
$ some-dep/ yarn link
$ some-dep/ cd ../main-app
$ main-app/ yarn link some-dep
$ main-app/ yarn start

This required us to change our workflow quite a bit. Before we'd write most of our UI code in React StoryBooks and then in the host app check things in parallel. Now we can only work in one app at a time. Also, if a sub-dep depends on another dep that contains styled-components and everything is linked together back to the main app, things seem to error out when rm -rf'ing styled-components through the complete dependency tree. I can expand on this if need be but haven't looked in a sec.

I remember a while back Emotion handled this SSR + yarn linking problem by allowing the sharing of a context, but I'm not sure what the status is on that.

@knieber
Copy link

knieber commented Mar 5, 2019

@probablyup @damassi appreciate the quick responses! I'm going to be trying both. Currently the steps in that script aren't working for me when using npm link instead of yarn link, so I'm going to try to refactor to yarn to see if that'll help. We have the same exact workflow for our UI team (Using storybook and having multiple component libs depend on other component libs). I may look into the sharing context solution as well and will holler if I find anything substantial.

@damassi
Copy link
Author

damassi commented Mar 5, 2019

We have the same exact workflow for our UI team (Using storybook and having multiple component libs depend on other component libs).

This is indeed a common workflow for apps and teams. It worked well with Styled Components 3 so I'm hopeful that a more formal solution can be discovered at some point. Thanks for looking into it @knieber!

@damassi
Copy link
Author

damassi commented Mar 7, 2019

Ok @knieber, @probablyup - I think I've found a potential solution via require-control. Hasn't been extensively tested, but looking good so far:

const { setAliases } = require("require-control")
const path = require("path")

setAliases({
  "styled-components": path.resolve(
    path.join(__dirname, "../node_modules/styled-components")
  ),
})

Obviously this isn't totally ideal as it's doing some require hacking but seems sufficient for our use-cases.

Let me know if this works for you.

@damassi
Copy link
Author

damassi commented Mar 7, 2019

Want to note that I discovered require-control via this react-hooks problems thread. When rolling hooks out to our codebase we ran into something similar as here.

@knieber
Copy link

knieber commented Mar 7, 2019

@damassi Sorry, just now getting back to this. Actually, I'm now having a problem with exports is not defined when linking our component library (build with babel) with our main app (using webpack on top of Next.js). A lot of things are abstracted away, so I'm getting a little lost on how to fix this problem first before getting styled-components to resolve. I'm assuming it's because of duping issue with babel (since babel is also used in our main app), but it's not as trivial as I expected it to be! It's only an issue when linking though. I'll update you once I get there!

Note: a temp workaround that we found was using Yalc to publish npm packages locally onto your machine, without actually publishing to npm. It's far less than ideal, but thought I'd spread the love.

@knieber
Copy link

knieber commented Mar 8, 2019

Finally figured out by babel dupe-ing issue (component lib had a babel-core 6 dependency from storybook) and just got back to the styled-components instancing issue. The require-control solution worked perfectly @damassi. Thanks so much!

@damassi
Copy link
Author

damassi commented Mar 8, 2019

Yeah! Our team was psyched to discover it. I should probably open a PR to update SC's docs.

@mxstbr
Copy link
Member

mxstbr commented Mar 9, 2019

We would love a PR @damassi!!!

@carlesnunez
Copy link

I've opened a pr in order to add this info to npm link issues on FAQ. Special kudos to @damassi for his amazing investigation in this issue. You saved my day dude!

styled-components/styled-components-website#468

@vongohren
Copy link

@knieber @damassi Im struggeling a bit with the same setup now.

I have a component library we want to use for web and SSR, which is styled with styled components. We run webpack and babal on this library so we can import it around.
But we also have yarn workspace, meaning the styled-components and the babel plugin is hoisted and then using the same package I guess.

When collecting styles from the SSR app, there is no CSS available. When collecting styles from the component library, the CSS is coming out when run on the server side, crashes when running in the browser of course.

Im just not seeing why this require would help for me? As they are already sharing packages.

Is it something to do with the webpacking im doing that the styles are dissapearing when loaded in an SSR environment?

Because after the client is loaded by next.js, the styles are also applied.

Im a bit lost :(
I tried emotion, that had more problems. I tried https://github.com/callstack/linaria, that worked, but it has not been updated in a while, and do not have react native support. That worked because you could extract css at compile time, and just import it in the ssr environment

@vongohren
Copy link

I have a reproducable setup here, if anyone are able to get any of the packages, preferebly styled-components, to work here. With no FOUC, I would be super happy!

https://github.com/vongohren/fouc-example

@vongohren
Copy link

@mxstbr any thoughts, base on this repo?

@mathiasgheno
Copy link

For those who're using lernajs adding styled-components as peerDependencies should work.

@WaleedAli070
Copy link

well, for me, adding styled-components to peerDependencies, in my lerna packages, didn't work with yarn link.

What worked for me was creating a .tgz file of my packages using npm pack and then installing these packages via npm install path/to/tgz in my consumer application.
The caveat was for each change in my library/packages I had to rebuild the package, clear the npm cache, and then reinstall these dependencies in my consumer application.

I know this is an ugly solution but this is the only one that worked for me.

@fritzlolpro
Copy link

What worked for me is aliasing styled components to the the instance I need. Without it styles probably got compiled with worng instance of SC.
In my next.config.js I added

module.exports = nextTranslate({
   //...
    webpack: (config, options) => {
        //...
        config.resolve.alias = {
            ...config.resolve.alias,
            'styled-components': path.resolve(
                path.join(__dirname, 'node_modules/styled-components')
            ),
        };
        return config;
    },
});

Hope this will help somebody (me in future for example)

@PrashanPudasaini
Copy link

I was facing similar issue where the myApp was not collecting styles from the symlinked module. I was able to resolve the issue by adding styled-components as a peer dependency in my component library and then doing npm link myApp/node_modules/styled-components from my library's root directory.

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

10 participants