Closed toddw closed 7 years ago
It would be nice to have a render-style interface and then you would have a Radium style interface an aphrodite style interface or people could write their own.
Do you have any concepts on how should it look like in your mind?
Also, I've seen Radium
integration is being removed in this PR https://github.com/TrueCar/gluestick/pull/386 - what's the status here, should we proceed with work here?
386 is a start, you can definitely pick up from where that one leaves off if it helps but it really only does some (possibly all) of the work for removing Radium but none of the work to make Radium or Aphrodite easy to turn on.
The way I think this should work is that a developer can specify a style engine in src/config/application.js
{..., styleEngine: gluestickRadiumEngine, ...}
And things should work with Radium. I don't know if we want to call it an engine or an interface.
What do you think about this approach? Definitely open to suggestions.
Do you think that it is that important to have style engine integrated into GlueStick? I think it's not that bad if we let the user handle it by himself, but that's just an idea. If you really want to have it integrated then I like your proposal, but I'm afraid that Radium, Aphrodite or StyledComponents have a bit different API, and it might be tricky to have it universal.
My goal is to make the simple stuff easy and the complex stuff possible… I do believe there is a way we can pull this off even if it will be tricky.
What do you think about an idea to give user option argument to new
command to choose radium
or aphrodite
?
Allowing the user to specify the style engine in the new
command sounds good to me, as long as it doesn't make it difficult for people to write their own style engine.
I'm going to spend some time today planning some possibilities for this functionality today. After thinking about it some more I have an idea of what might make this pretty easy to implement and give us everything we need.
First off I would like to say that I wish we could make this an app level concern but so far both Radium and Aphrodite have proven to need some changes high up in GlueStick in order to work. Because both Radium and Aphrodite require some extra work to setup, it would be nice if they were easily supported in GlueStick apps without too much work. One of our goals it to make building apps simple so if we can eliminate work in setting up the style engine then I think it is worth going after.
When we first started Radium seemed like a great tool (and it is!) but now there are tons of great tools and Aphrodite has proven to be something else we really want to use.
I think it will be pretty easy to support both and all of the other options going forward. I'm not sure if StyledComponents needs any integration at the Gluestick level but I would be happy to add that as an option as well.
Below is the breakdown for both Radium and Aphrodite regarding requirements.
https://github.com/FormidableLabs/radium
If we didn't need the user
https://github.com/Khan/aphrodite
const {html, css} = StyleSheetServer.renderStatic(() => {
return ReactDOMServer.renderToString(<App/>);
});
data-aphrodite
<style data-aphrodite>${css.content}</style>
StyleSheet.rehydrate
once the HTML is on the page but before ReactDOM.render is called in the browser.Both Aphrodite and Radium have different requirements but there is no reason we couldn't just create a plugable system that supports all of their needs.
// gluestick-radium-plugin
{
getRootComponent: (userAgent) => {…},
}
The function getRootComponent
will be passed the server request object so that it can get the user agent.
https://github.com/TrueCar/gluestick/blob/627e346d3e0ff27f9f4fdd1f35c24c0be25bc2cd/src/lib/server/RequestHandler.js#L139
The function would look like
function getRadiumRootComponent (userAgent) {
return function RadiumRootComponent({children}) {
return (
<StyleRoot radiumConfig={{userAgent}}>
{children}
</StyleRoot>
);
};
}
For Radium you would want to change: https://github.com/TrueCar/gluestick/blob/8ccd512ad8deb89c8f7fd948013c550db3b7573e/templates/new/src/config/.entry.js#L39
Instead of passing radiumConfig
it would just pass userAgent
. Now if any other style engine needed a custom root component at the top of the app, it could be done. It would be tempting to pass something other than userAgent
but you'll notice that this is built on the server for server side rendering and in the client. So sometimes we get the user agent from req.headers
and then in the browser we get it from window.navigator.userAgent
. I also can't think of anything else that style engine might need. Radium uses this for browser specific prefixes on styles.
Aphrodite takes a bit more work to make a plugin. All of the functionality to support Aphrodite already exists in a GlueStick app but there are a lot of steps so it be nice to make it easier.
// gluestick-aphrodite-plugin
{
renderMethod: (render, main) => {…},
preRenderMethod: () => {…}
}
renderMethod is supported in src/config/application.server.js https://github.com/TrueCar/gluestick/blob/627e346d3e0ff27f9f4fdd1f35c24c0be25bc2cd/src/lib/server/RequestHandler.js#L163-L173
An example Aphrodite render method would look like this:
import React from "react";
import { StyleSheetServer } from "aphrodite";
import { renderToString } from "react-dom/server";
export default function renderWithAphrodite (render, main) {
const { html: body, css } = StyleSheetServer.renderStatic(() => {
return renderToString(main);
});
const head = [
<style key="style" data-aphrodite>{css.content}</style>,
<meta key="meta" name="aphrodite-styles" content={JSON.stringify(css.renderedClassNames)} />
];
return {
body,
head
};
}
An example Aphrodite preRenderMethod will look like this:
import { StyleSheet } from "aphrodite";
const styles = JSON.parse(document.querySelector("meta[name='aphrodite-styles']").getAttribute("content"));
StyleSheet.rehydrate(styles);
The renderMethod
function follows the instructions for generating styles with server side rendering as described on Aphrodite. With the slight difference being the head and body tags will be passed back to GlueStick to handle. The head tags will be embeded in the head of the document and body will be the HTML.
With the custom render method, then we can make sure Aphrodite can rehydrate the styles based on the data that was passed to the meta tag during the render method.
In order to use Radium or Aphrodite apps will also need to include the node_module in the package.json. I'm not sure of a great way to solve this yet.
An idea: The interface supports:
{
// …,
// …,
dependencies: {
"radium": ">=1.0.0"
}
}
When you start up the app we could modify src/autoUpgrade/updatePackage.js so it looks at the style engine's dependencies and makes sure they are installed when you start the app.
Hopefully the specific details of how to support Radium or more likely how to support Aphrodite didn't make this seem to indimidating. I'm proposing a GlueStyle style engine interface looks like the following:
{
getRootComponent: (userAgent) => {…}, // optional (needed for Radium engine)
renderMethod: (render, main) => {…}, // optional (needed for Aphrodite engine)
preRenderMethod: () => {…} // optional (needed for Aphrodite engine),
dependencies: {…}
}
If any of the properites are missing we can have a sensible fallback:
getRootComponent
can be a simple component that renders <div>{children}</div>
renderMethod
we can simply check if it exists on this line: https://github.com/TrueCar/gluestick/blob/8ccd512ad8deb89c8f7fd948013c550db3b7573e/src/lib/server/RequestHandler.js#L163
const renderMethod = config.server && config.server.renderMethod || styleEngine.renderMethod;
preRenderMethod
- We can just add a line above this one: https://github.com/TrueCar/gluestick/blob/8ccd512ad8deb89c8f7fd948013c550db3b7573e/templates/new/src/config/.entry.js#L33
That simply says styleEngine.preRenderMethod && styleEngine.preRenderMethod();
As most style engines for React are created, and when demand for supporting other currently existing engines arises we can extend this interface if we ever need to.
The style engine plugins should probably live on their own outside of the main gluestick
repo. Maybe we should create modules for gluestick-style-radium
, gluestick-style-aphrodite
?
What do you all think?
Also, this leaves open the question: What should be the default? I personally think glustick-style-aphrodite
should be the default moving forward.
And yes I agree with @lukewalczak that it would be nice to specify the style plugin when running gluestick new
. Maybe it would look like gluestick new --style-engine=gluestick-style-aphrodite
?
I know it is possible to use Aphrodite with GlueStick but it would be nice if it were easier to set up. Currently you have to override
renderMethod
in src/config/application.server.js then you have to run through theStyleSheetServer.renderStaticMethod
as documented in the aphrodite docs which gives you the html and the css then you expose the html asbodyContent
in the output of the render method and you have to set up the stylesheet head tag with the css exported fromStyleSheetServer.renderStaticMethod
and then you need to add code tosrc/config/init.browser.js
to rehydrate aphrodite before the app is mounted.It's all possible and we have done it but it would be nice if it were easier.