-
Notifications
You must be signed in to change notification settings - Fork 262
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
feat: Allow providing custom StyleSheetRegistry (fix async render issues) #703
Conversation
This does seem to work in my NextJS codebase. // _document.tsx (abridged)
class AppDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
// create a per-request registry
const styledJsxRegistry = new StyleSheetRegistry();
// override renderPage (as suggested in NextJS documentation)
const originalRenderPage = ctx.renderPage;
ctx.renderPage = (opts) => {
return originalRenderPage({
...opts,
enhanceApp: (App) => (props) => (
<StyleSheetRegistryContext.Provider value={styledJsxRegistry}>
<App {...props} />
</StyleSheetRegistryContext.Provider>
),
});
};
const initialProps = await Document.getInitialProps(ctx);
const styledJsxStyles = flushToReact({ registry: styledJsxRegistry });
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{styledJsxStyles}
</>
),
};
}
...
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @travigd thanks for the PR!! So far so good! Can you add some tests? @ me when the PR is ready.
A few comments:
@@ -212,6 +213,11 @@ export default class StyleSheetRegistry { | |||
} | |||
} | |||
|
|||
export const globalStyleSheetRegistry = new StyleSheetRegistry() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately the constructor of StyleSheetRegistry
creates a new StyleSheet instance and injects it into the page. This would be useless when you don't use the global registry/stylesheet.
Perhaps we can create a global and shared StyleSheetRegistry
in style.js
only when this.context
is undefined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two things:
First thing:
The predominant use case for this will be server rendering, where the sheet isn't injected (since it's on the server). When client rendering, we can just use the global instance.
Second thing:
Are you sure about that assessment? Either way, the StyleSheet
gets injected when StyleSheetRegistry
is constructed. If that happens only during SSR, it's a no-op, but if you're using StyleSheetRegistryContext
as part of CSR, you'll still presumably construct a new StyleSheetRegistry
and it will be injected into the DOM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way, the StyleSheet gets injected when StyleSheetRegistry is constructed.
Correct
but if you're using StyleSheetRegistryContext as part of CSR
If you used multiple StyleSheetRegistry
each using a dedicated StyleSheet, you'll have the same amount of StyleSheets on the client (due to hydration the same code that ran on the server will run on the client).
What I meant with my previous comment is that if you don't use the globalStyleSheetRegistry
instance, on the client you'll have one extra (unused) <style> element.
Probably not a big deal and the following is not worth and ugly, but the above could be avoided if in style.js
you do the following:
let globalStyleSheetRegistry = null
export default class JSXStyle extends Component {
// ...
get styleSheetRegistry() {
if (this.context instanceof StyleSheetRegistry) {
return this.context
}
if (!globalStyleSheetRegistry) {
globalStyleSheetRegistry = new StyleSheetRegistry()
}
return globalStyleSheetRegistry
}
}
with
export const StyleSheetRegistryContext = React.createContext(null)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see what you mean.
Personally, I think having the global registry just always present is fine even if it's empty (especially since I suspect the predominant use case will only be wrapping it for a SSR then using the global registry for everything on the client). My NextJS example that I added doesn't suffer from this for exactly that reason (the _document code only runs on the server).
If you'd really like me to change something, let me know I can do it. But like I said, I think the current behavior in this PR is fine.
@giuseppeg I'm happy with this PR. If we really want to preserve compat with React <16.3, we can probably hack it in (with conditional EDIT: Prettier decided to update the entire README as part of the commit hook, but the only thing that I changed was the section I added about asynchronous rendering. |
static dynamic(info) { | ||
return info | ||
.map(tagInfo => { | ||
const baseId = tagInfo[0] | ||
const props = tagInfo[1] | ||
return styleSheetRegistry.computeId(baseId, props) | ||
return computeId(baseId, props) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason for not passing a cache
here? Probably it is ok to export a version with a built in cache (the iife) I wrote in the other comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do that. My only worry is just that it might become a bit of a memory leak since you have a cache entry per unique set of props (so if you have a long running app that uses different interpolations, they'd never get cleared).
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh well it has been like this forever and nobody has reported any issue of this kind. If necessary we can replace the object with a WeakMap any time though (later)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say let's fix this (bring back the cache)
Hey @giuseppeg , just bumping this. No huge rush but would love to get this merged and released. 🎉 |
Hi @travigd sorry for making you wait – I haven't had time to check the PR again but it looks good, there are just a couple of unresolved comments (especially the one about the cache) that might be worth fixing. @timneutkens can you take a look at this PR when you have time? I just want to make sure that these changes won't conflict with future plans of yours (assuming styled-jsx will be updated to support concurrent mode or server components when they are out). |
Yep! Thank you! I did respond to the caching thing as being a potential memory leak if we use a global cache that is never flushed during SSR. :) |
ping @timneutkens |
@timneutkens @giuseppeg Any way we can move forward with this? :) |
Overall this looks good, however I'm not convinced we should provide backwards compat with the previous approach, it'll likely be a lot simpler to release a new major version that does not support the global registry which is an anti-pattern nowadays anyway 👍 The version in Next.js is pinned so that would not affect existing apps. |
If I get a say, I'd suggest releasing this as a minor then re-write the code base to include a few breaking changes (like this) and also adopt typescript and maybe reduce reliance on snapshot testing while we're at it. |
Overall there's no benefit in releasing a minor though as styled-jsx is mostly used with Next.js. |
Maybe I'm confused, but... I was able to install this version onto my NextJS project and it works just fine. |
This issue #714 made me realize that if we want to support multiple registries we'd need to find a way to hydrate SSR styles. Currently the first instance of the stylesheet registry will handle those styles styled-jsx/src/stylesheet-registry.js Lines 202 to 205 in 2741879
|
We landed a new context based API in v5 beta. Please checkout the readme for the information. Thanks for the PR! |
Should fix #64 (while trying to keep existing workflows working and changing as few lines of code as possible).
Changes
StyleSheetRegistryContext
(whose default value isglobalStyleSheetRegistry
-- means should work without changes for existing users who don't care about async rendering)This is still somewhat of a WIP, but I think it's at the point that it could be review.