I just want to kick off a discussion to finalise our SSR API.
So API came from the comments on the #214
const { css, html } = StyleSheet.collect(() =>
ReactDOMServer.renderToString(<App />)
);
Please feel free to suggest what the API will look like, (let the bikeshedding begin!)
Also if we're already doing de-duping in SSR level, do we even need reset()? (related issue #378) Or would that just be a convenience function since we don't need it anymore?
cc @mxstbr / @geelen
One thing I would throw out there is that it would be very useful to be able to have separate bundles of CSS. That way, you could target styles to a style tag or to a separate stylesheet. When I worked for a publisher, we used this type of optimization to achieve very fast initial renders, with nonessential styles deferred until later.
I can ticket this separately, if that would be helpful. It would require some sort of ability to annotate at the component or CSS level where rulesets would be targeted, and an API for collecting the separate bundles (which might be a collect variant).
This happens either way @acjay. With styled-components, only the styles of components that are actually used on the initial render are injected đ
Generating .css files for server-side rendered apps would not be complicated to add, but should not live in core. (it's more or less just a fs.write(css) away) We're going to work on a babel or webpack plugin to make this possible for non-ssr apps but that's a different issue. (see #59)
@thisguychris, reset() is great for doing testing. Considering that tagged templates have logic in them, that logic may need to be tested.
That's a great point actually @dfrankland, I hadn't even considered that use case!
Looks good to me
Do we also want to be able to export class names here ( in addition to CSS and HTML) like Aphrodite and Glamor does so that we can consume them on the front end to make sure things stay in sync or are just going to rely on the babel plugin to ensure deterministic class names?
Even if the names are deterministic, we still want a list of the class names, so we don't inject every rule twice. Either that, or maybe we could have a class attribute on style and links like __styled-component-sheet__, and then just build the list from those sheets.
I don't think we need to worry about duplicate classnames after SSR; just remove the server-provided style block after the app code arrived and let S-C rebuild the styles.
I like what Aphrodite does. Just pass the classnames along with the initial state and rehydrate without rebuilding client-side. Does anyone see any cons with this approach?
Oh right that's a different issue, that should be handled by the Babel
plugin.
Actually, I am running v2 with the plugin and it doesn't work for me, the
generated classnames change between server and client.
I don't understand how the names can differ though; given that the entire
render result is normally the same, and the order of evaluation thus also,
shouldn't the generated names be the same as well? If you use a
deterministic naming scheme, that is?
On Wed, Feb 1, 2017 at 4:09 PM Nick Randall notifications@github.com
wrote:
@wmertens https://github.com/wmertens we do need to worry about
duplicate class names because react will check the root elements html on
its first render and see if it is the same. If it is then react will resuse
it, otherwise it will blow it away and start from scratch (thus defeating
the purpose of srr). If the class names are differeent the that first check
won't pass.â
You are receiving this because you were mentioned.Reply to this email directly, view it on GitHub
https://github.com/styled-components/styled-components/issues/386#issuecomment-276681880,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AADWllSu0cPLYFXjuDE-W4C8oTCmFuEMks5rYKBDgaJpZM4LnSjo
.
@wmertens I found that in order to get the names to match, I had to make sure each file was transpiled via babel once, and then the result re-used on client and server. I was initially applying babel twice (once for the server side and once for the client side). This was resulting in different names.
It would be cool if in the babel plugin, we could make the names deterministic (e.g. use an md5 hash of the file's source)
:( I can't do that, the Babel options for client and server differâŠ
Strangely, before using the plugin, the names given by S-C were fineâŠ
On Thu, Feb 2, 2017, 1:13 AM Forbes Lindesay notifications@github.com
wrote:
@wmertens https://github.com/wmertens I found that in order to get the
names to match, I had to make sure each file was transpiled via babel once,
and then the result re-used on client and server. I was initially applying
babel twice (once for the server side and once for the client side). This
was resulting in different names.It would be cool if in the babel plugin, we could make the names
deterministic (e.g. use an md5 hash of the file's source)â
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/styled-components/styled-components/issues/386#issuecomment-276825946,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AADWlhmKHwxt_XPBUdHM8cSPLJI7uTz3ks5rYR-6gaJpZM4LnSjo
.
I am experiencing the same @wmertens
I think possibly the simplest option might be some kind of .styled-components-cache.json file that could map sha hashes of files + index of styled component to generated class name. That way we could ensure they were always deterministic, without having to make them long enough to avoid collisions.
On the other hand, if they're currently just random IDs, maybe just converting them to the first few letters of the sha has would be good enough.
For SSR, all you need is "the first one, the second one, ..." since on the
client they will be encountered in the same order. So class names can just
be a prefix with a number.
Right?
On Thu, Feb 2, 2017 at 11:13 AM Forbes Lindesay notifications@github.com
wrote:
I think possibly the simplest option might be some kind of
.styled-components-cache.json file that could map sha hashes of files +
index of styled component to generated class name. That way we could ensure
they were always deterministic, without having to make them long enough to
avoid collisions.On the other hand, if they're currently just random IDs, maybe just
converting them to the first few letters of the sha has would be good
enough.â
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/styled-components/styled-components/issues/386#issuecomment-276917712,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AADWlk0gwUTxIXJR7AtPOo6c5__S5B1kks5rYaw_gaJpZM4LnSjo
.
@wmertens There are a lot of ways the client and server execution could be different. And I think it's unreasonable to force that restriction on people.
e.g.
if(isBrowser) {
styled.div``
}
could potentially break everything.
The contract for SSR is that the first client render has to do the same
thing as the server, otherwise React complains and it cannot reuse the
existing DOM.
On Thu, Feb 2, 2017, 8:32 PM Zach Smith notifications@github.com wrote:
@wmertens https://github.com/wmertens There are a lot of ways the
client and server execution could be different. And I think it's
unreasonable to force that restriction on people.e.g.
if(isBrowser) {
styled.div``
}could potentially break everything.
â
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/styled-components/styled-components/issues/386#issuecomment-277053441,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AADWll6J8TrTZ6Le9KHwk9DYeA2C-cMZks5rYisagaJpZM4LnSjo
.
Yes, but as @xcoderzach points out, you can declare components that will be used on the second render of the client side code, but that you don't need on the server side.
The babel plugin is definitely the right approach. I think the only possible issue is that I don't think we can rely on files always being processed in the same order by babel. e.g. babel src --out-dir lib would be very different to browserify src/index.js -t babelify. This is why I don't think our solution should rely on random number generation or the order in which files are processed.
@ForbesLindesay I'm on making id's deterministic.
I have been asked by @mxstbr to comment on that issue.
The block API proposed by @satya164 looks definitely simpler than the getCSS()/reset() one. The teardown is handled for you.
However, my concern with those two APIs is around request isolation and concurrency. Relying on a singleton to collect the style prevents from handling two requests concurrently.
We have been implementing a different API with JSS on the new version of Material-UI. We instantiate a style collector at each request.
This example is taken from the Server Rendering documentation.
function handleRender(req, res) {
// Create a styleManager instance.
const { styleManager, theme } = createStyleManager();
// Render the component to a string.
const html = renderToString(
<MuiThemeProvider styleManager={styleManager} theme={theme}>
<App />
</MuiThemeProvider>
)
// Grab the CSS from our styleManager.
const css = styleManager.sheetsToString()
// Send the rendered page back to the client.
res.send(renderFullPage(html, css))
}
To be fair, that API only shine when using a streaming rendering implementation that can be interrupted. When using the synchronous ReactDOMServer.renderToString() implementation, I don't think that it makes much different. Maybe, it's simpler with unit tests.
On Mon, Feb 13, 2017 at 1:32 PM Olivier Tassinari notifications@github.com
wrote:
To be fair, that API only shine when using a streaming rendering
https://github.com/aickin/react-dom-stream implementation that can be
interrupted. When using the synchronous ReactDOMServer.renderToString()
implementation, I don't think that it makes much different. Maybe, it's
simpler with unit tests.
I like it! Besides, people can decide to use streaming rendering with S-C,
so definitely a good thing.
Looks like asynchronous SSR is now a real thing đ https://github.com/FormidableLabs/rapscallion.
I would also very much like to be able to use this in an asynchronous streaming manner.
Landed here while debugging a rapscallion + styled-components implementation.
Does anyone have a working example đ
@mxstbr?
I think https://github.com/FormidableLabs/rapscallion/issues/39 is probably
a better issue for that question?
On Sun, Mar 19, 2017, 11:14 PM Siddharth Kshetrapal <
[email protected]> wrote:
Landed here while debugging a rapscallion + styled-components
implementation.Does anyone have a working example đ
@mxstbr https://github.com/mxstbr?
â
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/styled-components/styled-components/issues/386#issuecomment-287652271,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AADWltIZPYFKEX_h0V5ISSuqxQawsC2Cks5rnajCgaJpZM4LnSjo
.
nfl/react-helmet (head element side-effect component) have a single method call to handle server side rendering. Using it in a large production application with great success.
https://github.com/nfl/react-helmet#server-usage
@mxstbr With that example, how would the styleSheet instance be used during renderToString if itâs not passed in anywhere? I think most solutions put the instance in the React Context, so itâs available everywhere, but I donât see that being done in your example.
Wait⊠am I crazy? Where did that example that I received an email about go? đ€
With React v16 (Fiber) vanilla streaming SSR support is on the roadmap, so we really want an API that works with that.
Since the classes from the Babel plugin are now deterministic, the TL;DR of this discussion, as far as I can tell, is that we need a non-singleton for concurrency and a single method rather than two separate ones.
This doesn't have to be too different from what we already have. If we export a non-singleton that means we can still have the getCSS and reset methods, but you won't have to use reset on the server since it's not a singleton anyway. (still useful for testing though)
The one thing that needs to change is that we need to keep track of which CSS was injected in that request. I quite like the API that was suggested by @oliviertassinari (thanks for chiming in btw), though I don't think the ThemeProvider should care about the style manager, that should be its own thing imo.
Taking all of those thoughts into account the API would look something like this:
import { getStyleManager } from 'styled-components';
function route(req, res) {
// Get the styleSheet for this request
const { StyleManager, styleSheet } = getStyleManager();
// Render the app
const html = renderToString(
<StyleManager styleSheet={styleSheet}>
<App />
</StyleManager>
);
// Get the critical CSS
const css = styleSheet.getCSS();
// Send the response
res.send(render(html, css));
}
Does this cover everything? The big change to the current API would be that the styleSheet can either be constructed client-side or come from context.
Aha, I guess you deleted it and added a new one that _does_ show the instance being injected :) đ
One question re streaming, though, it looks like with this example the full render has to be performed before sending any data to the client, how would the streaming version work?
Yeah sorry, I pressed "Comment" too fast...
One question re streaming, though, it looks like with this example the full render has to be performed before sending any data to the client, how would the streaming version work?
That depends on how React Fiber implements it, I have no idea! Probably something like this?
const html = renderToString(
<StyleManager styleSheet={styleSheet}>
<App />
</StyleManager>
);
const css = styleSheet.getCSS();
render(html, css).toStream().pipe(res);
To be clear, I have no idea either and no previous experience with this. Conceptually it seems tough as we'd want the styling in the head element, but the CSS isn't known until rendering is done. Maybe this can only be done with ahead of time compilation of the CSS or caching it after the first request or something?
Are there examples of other frameworks that already do this?
Conceptually it seems tough as we'd want the styling in the head element, but the CSS isn't known until rendering is done.
You are right, that is going to be a pain. At least the new API makes concurrency possible, I don't know if streaming SSR is ever going to work with css-in-js though, given we don't know which CSS to inject until everything is rendered... :confused: /cc @trueadm @gaearon
At least the new API makes concurrency possible
True đ
I don't know if streaming SSR is ever going to work with css-in-js though
Inline styles would probably work, but frameworks that collect styles and want to inject into head seem to be impossible yeah.
Is the babel plugin still meant to be able to extract all styling and serve that as a precompiled stylesheet? I guess that might be the only way to add streaming.
Talking to @trueadm it's pretty clear for streaming rendering support we'll need to have a static CSS file. The babel/webpack plugin is going to get us pretty far, but not all the way there.
Rather, we'll need to simulate as if a real style sheet existed with the generated styles from the render. This would be done by dynamically injecting a <link> into the streamed HTML which points towards the right CSS in the cache.
Basically, it'd look something like this: (note, pseudo code)
const styleCache = {};
app.get('/stylesheet.css?:id', (req, res) => {
// Pretend as if a style sheet exists here and send back the generated styles for this request
res.send(styleCache[req.params.id]);
})
app.get('*', (req, res) => {
const { StyleManager, styleSheet } = getStyleManager();
const id = generateUniqueHash();
renderToStream(
// This renders a <link href=`/stylesheet.css/?${id}` />
<StyleManager styleSheet={styleSheet} styleId={id}>
<App />
</StyleManager>
).pipe(res);
// Cache the CSS of this request after the stream has finished rendering
// TODO: Figure out how to do this after the stream has finished rendering, for now let's
// assume it's synchronous
styleCache[id] = styleSheet.getCSS();
})
No matter if this exact code works out or not, I'm pretty confident that switching to a non-singleton export will make lots of these things possible and should be pretty future-proof, so I think I'm all for this.
As proposed above, this would be the full API extension for styled-components:
import { getStyleManager } from 'styled-components';
// Returns new instances of these every time
const { StyleManager, styleSheet } = getStyleManager();
// Passes the style sheet to styled-components internally via context rather than using the general one
<StyleManager styleSheet={styleSheet} />
// Returns a string of CSS
styleSheet.getCSS();
// Resets the current state of the style sheet, mostly necessary for testing
styleSheet.reset();
Does anybody have input regarding naming or use cases this doesn't cover? Should we still have the general styleSheet export, which would export the general style sheet that's used if none is provided via a StyleManager for testing? (I'm thinking yes)
Otherwise I say we go ahead and start implementing a WIP of this!
Sorry, I'm a bit out of loop here
@oliviertassinari : However, my concern with those two APIs is around request isolation and concurrency. Relying on a singleton to collect the style prevents from handling two requests concurrently.
What I proposed doesn't need to rely on a singleton, it can just manage creating new instance internally instead of exposing it and pass the instance as a prop.
What do you mean by handling two requests concurrently? Can you give an example?
@mxstbr
Wondering what's the advantage of the reset method as opposed to just creating a new styleSheet object if you need, also why do we need a new instance of StyleManager each time? Won't <StyleManager /> give us a new react element every time?
import { StyleManager, StyleSheet } from 'styled-components';
const styleSheet = StyleSheet.create();
<StyleManager styleSheet={styleSheet}><MyApp></StyleManager>
styleSheet.getCSS();
Though I'm wondering, what's the purpose of the manually creating the styleSheet object. Where else it will be used? Am I going to use it to render multiple elements and collect css from each of them?
Wondering what's the advantage of the reset method as opposed to just creating a new styleSheet object if you need
Makes testing easier:
import { styleSheet } from 'styled-components'
beforeEach(() => {
styleSheet.reset();
})
why do we need a new instance of StyleManager each time? Won't
give us a new react element every time?
That's a good point, I quite like your API!
The purpose of the separate styleSheet object is to avoid having two request referring to the same CSS when concurrently rendering, what @oliviertassinari said. (I have no idea is anybody is doing that or if that problem exists, I don't really do SSR, but it seems like a likely issue to come up)
@mxstbr
Aren't these almost same? I don't like extra methods xD
const styleSheet = createStyleSheet() // `StyleSheet.create()` or `getStyleManager()`
beforeEach(() => {
styleSheet.reset();
})
vs
let styleSheet = createStyleSheet() // `StyleSheet.create()` or `getStyleManager()`
beforeEach(() => {
styleSheet = createStyleSheet()
})
The purpose of the separate styleSheet object is to avoid having two request referring to the same CSS when concurrently rendering
Yeah, I get that. I was asking what's the purpose of making the user manually create the instance as opposed to automatically handling it internally. e.g. -
const { css, html } = StyleSheet.collect(styleSheet =>
ReactDOMServer.renderToString(<App styleSheet={styleSheet} />)
);
Aren't these almost same?
Yeah you're right, if we go with StyleSheet.create() that does the same thing.
How would your API work with streaming SSR? (i.e. styleCache)
@mxstbr html and css could be streams instead of strings perhaps, the css gets populated as new html is rendered. Though I don't have experience with streaming rendering, so I'm not totally sure.
You would probably do something like this:
const { StyleSheet, StyleManager } = getStyleManager();
const { css } = StyleSheet.collect(styleSheet => {
renderToStream(<StyleManager styleSheet={styleSheet}><App /></StyleManager>).pipe(res);
})
Hmm, I quite like that actually as it forces people to work concurrently...
Ok just catching up here. Let me know if I've missed anything, but it seems like this would work:
import { StyleSheet } from 'styled-components'
const { css, html } = StyleSheet.collect(withStyleSheet => (
ReactDOMServer.renderToString(withStyleSheet(<App/>))
))
This potentially lends itself to some kind of streaming API:
import { StyleSheet } from 'styled-components'
const css = new Stream()
const html = new Stream()
StyleSheet.toStream(withStyleSheet => {
renderToStream(withStyleSheet(<App/>)).pipe(html)
}).pipe(css)
Personally, I prefer the idea of a HOC for attaching a stream rather than a <StyleManager>.
Thoughts?
It's not HoC because it doesn't transform a component, but does something
with an element. However, I guess it works as long as the resultant React
tree is the same as on the client.
On Mon, Apr 3, 2017, 11:45 AM Glen Maddern notifications@github.com wrote:
Ok just catching up here. Let me know if I've missed anything, but it
seems like this would work:import { StyleSheet } from 'styled-components'
const { css, html } = StyleSheet.collect(withStyleSheet => (
ReactDOMServer.renderToString(withStyleSheet())
))This potentially lends itself to some kind of streaming API:
import { StyleSheet } from 'styled-components'
const css = new Stream()const html = new Stream()
StyleSheet.toStream(withStyleSheet => {
renderToStream(withStyleSheet()).pipe(html)
}).pipe(css)Personally, I prefer the idea of a HOC for attaching a stream rather than
a. Thoughts?
â
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/styled-components/styled-components/issues/386#issuecomment-291096596,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AADWlil3YeGLioYVYAnHDPnNyh908PLzks5rsL_OgaJpZM4LnSjo
.
Frankly, I don't really see the point of streaming the css bit. It's being built up gradually as the components are rendered, and we want it to be injected at the bottom of the HTML then, I guess? So by the time we reach that bit of the HTML, the css should be complete and doesn't need to be streamed, right?
@mxstbr I donât see how the suggested cached CSS approach would really work. The CSS would still only be complete by the end of rendering the full tree, so when _streaming_ the HTML to the browser it would fail to fetch the CSS once itâs received the head element, no?
The babel/webpack plugin is going to get us pretty far, but not all the way there.
What would it not be able to cover?
However, I guess it works as long as the resultant React tree is the same as on the client.
The DOM will be the same, there'll only be an _element_ more on the server. (<StyleManager />) That shouldn't be an issue, right?
I like that API @geelen, less surface area is great!
Not a fan of StyleSheet.collect(withStyleSheet) in terms of naming though, as first we're collecting a stylesheet but then adding another stylesheet? Sounds pretty confusing. What about this instead:
import { StyleSheet } from 'styled-components'
const { css, html } = StyleSheet.extract(collectStyles => (
renderToString(collectStyles(<App/>))
))
I agree that you wouldn't stream the CSS, so this should work fine.
so when streaming the HTML to the browser it would fail to fetch the CSS once itâs received the head element, no?
Yeah, you're right, you'd need to inject the <link /> at the end of the HTML rather than the beginning.
What would it not be able to cover?
Anything dynamic will have to stay at runtime, so we can't just use the webpack plugin and be done with it.
The React tree has to be the same on client and server for rehydration.
Also, if the CSS is at the bottom of the page, there will be a FoUC unless
you have a rule that hides the page until after the CSS loaded? For a long
page (e.g. blog post with comments), that could be a long wait.
Alternative solution: embed
Most helpful comment
For anyone who ends up here and is searching for some documentation. There's some text explaining it briefly on the ~upcoming~ docs: ~https://sc-next-docs.philpl.com/docs/advanced#server-side-rendering~
Edit: As @ChingxWando says below, the website is now live. So the new link is: https://www.styled-components.com/docs/advanced#server-side-rendering