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

figure out how to split & reference css by chunk #14

Closed
mattkrick opened this issue Dec 17, 2015 · 27 comments
Closed

figure out how to split & reference css by chunk #14

mattkrick opened this issue Dec 17, 2015 · 27 comments

Comments

@mattkrick
Copy link
Owner

this is why inline styles suck.

@mattkrick
Copy link
Owner Author

Currently: the client build has 1 main .css file, kinda like a common chunk. This is great because css gets loaded incrementally. Unfortunately, the server is all 1 big chunk and 1 big css file.
Solution: extract all css chunks into 1 file. This isn't ideal because we shouldn't load what we don't need

Ideally, the SSR bundle should be broken out by chunk and react-router should be smart enough to fetch those chunks to serve. Currently, it gets list in match somewhere.

@mattkrick
Copy link
Owner Author

Now serving 1 css file for the whole app.
This is good because the whole site is painted with just 15kB, super fast.
This is bad because as the site gets more entry points, it'll be more wasted css sent to the client.

Current alternatives:

The right way:

  • each entry point downloads 1 css file
  • each async chunk has its own css file

Alternative:

  • each entry point downloads 1 css file
  • each async route looks up the chunk it needs to serve
  • derive the css from the chunk's deps
  • put all those css chunks inside a style tag & ship it to the client

@mattkrick mattkrick changed the title FOUC on material-ui components FOUC on code-split routes Dec 18, 2015
@mattkrick mattkrick changed the title FOUC on code-split routes figure out how to split & reference css by chunk Dec 20, 2015
@stereobooster
Copy link

Maybe something like critical path approach. Plus take a look at amp for speed up techniques

@mattkrick
Copy link
Owner Author

neat, never knew about amp before. not sure how well it'll play with webpack, but i'll give it a go.
critical path is interesting, you're saying treat async loaded websites the same as if they're above the fold?

@mattkrick
Copy link
Owner Author

also note webpack/webpack#1758

@stereobooster
Copy link

Other note on critical path technique http://techblog.netflix.com/2015/08/making-netflixcom-faster.html

@patrickleet
Copy link
Contributor

@mattkrick
Copy link
Owner Author

I remember playing with this when it first came out and there was something
that it couldn't do so I couldn't adopt it, but now I can't remember what
that was... Could you try a very small proof of concept PR?

With defined landing/admin/user pages it really makes sense to have (at
least) 3 separate CSS files to minimize initial payload. But, webpack
doesn't support that because async loading a CSS file means a race
condition exists between the CSS and the js, so you could encounter a fouc
when you load a new module.

If you loaded the CSS via js, then the page looks bad for those with js
disabled. Not terrible, but it'd be preferable to at least show a
respectful "enable your JavaScript you tool" on the landing page.

So this would lead me to believe that the best solution would be a CSS
extract that contains just enough CSS for the landing module, ignore the
rest of the extracts, and stick the remainder in style tags as they arrive.
Not sure how to do this without the server build, or if this is the best
solution, but I'm open to ideas!

On Wed, Feb 24, 2016, 7:52 AM Patrick Scott [email protected]
wrote:

https://github.com/martinandert/babel-plugin-css-in-js


Reply to this email directly or view it on GitHub
#14 (comment).

@patrickleet
Copy link
Contributor

I spent the past several days playing with radium and actually had great
success with it. I removed all CSS from my app.

I thought I'd encounter more issues but it came out well. Even able to use
normalize CSS and bootstrap grid via {Style}

I can put a PR together for that. It's kinda nice to be able to just remove
all CSS loaders. I even wrapped up all the graphiql CSS into JS.

Getting 93 and 94 page speed for mobile and desktop and there are still
optimizations to be made. (Mostly setting up a reverse proxy) Downloading
blocking CSS hurts, and now I don't have to worry about that anymore :)

On Thursday, February 25, 2016, Matt Krick [email protected] wrote:

I remember playing with this when it first came out and there was something
that it couldn't do so I couldn't adopt it, but now I can't remember what
that was... Could you try a very small proof of concept PR?

With defined landing/admin/user pages it really makes sense to have (at
least) 3 separate CSS files to minimize initial payload. But, webpack
doesn't support that because async loading a CSS file means a race
condition exists between the CSS and the js, so you could encounter a fouc
when you load a new module.

If you loaded the CSS via js, then the page looks bad for those with js
disabled. Not terrible, but it'd be preferable to at least show a
respectful "enable your JavaScript you tool" on the landing page.

So this would lead me to believe that the best solution would be a CSS
extract that contains just enough CSS for the landing module, ignore the
rest of the extracts, and stick the remainder in style tags as they arrive.
Not sure how to do this without the server build, or if this is the best
solution, but I'm open to ideas!

On Wed, Feb 24, 2016, 7:52 AM Patrick Scott <[email protected]
javascript:_e(%7B%7D,'cvml','[email protected]');>
wrote:

https://github.com/martinandert/babel-plugin-css-in-js


Reply to this email directly or view it on GitHub
#14 (comment).


Reply to this email directly or view it on GitHub
#14 (comment).

@patrickleet
Copy link
Contributor

This also makes it trivial to code split, as everything is just JS

On Thursday, February 25, 2016, Patrick Scott [email protected]
wrote:

I spent the past several days playing with radium and actually had great
success with it. I removed all CSS from my app.

I thought I'd encounter more issues but it came out well. Even able to use
normalize CSS and bootstrap grid via {Style}

I can put a PR together for that. It's kinda nice to be able to just
remove all CSS loaders. I even wrapped up all the graphiql CSS into JS.

Getting 93 and 94 page speed for mobile and desktop and there are still
optimizations to be made. (Mostly setting up a reverse proxy) Downloading
blocking CSS hurts, and now I don't have to worry about that anymore :)

On Thursday, February 25, 2016, Matt Krick <[email protected]
javascript:_e(%7B%7D,'cvml','[email protected]');> wrote:

I remember playing with this when it first came out and there was
something
that it couldn't do so I couldn't adopt it, but now I can't remember what
that was... Could you try a very small proof of concept PR?

With defined landing/admin/user pages it really makes sense to have (at
least) 3 separate CSS files to minimize initial payload. But, webpack
doesn't support that because async loading a CSS file means a race
condition exists between the CSS and the js, so you could encounter a fouc
when you load a new module.

If you loaded the CSS via js, then the page looks bad for those with js
disabled. Not terrible, but it'd be preferable to at least show a
respectful "enable your JavaScript you tool" on the landing page.

So this would lead me to believe that the best solution would be a CSS
extract that contains just enough CSS for the landing module, ignore the
rest of the extracts, and stick the remainder in style tags as they
arrive.
Not sure how to do this without the server build, or if this is the best
solution, but I'm open to ideas!

On Wed, Feb 24, 2016, 7:52 AM Patrick Scott [email protected]
wrote:

https://github.com/martinandert/babel-plugin-css-in-js


Reply to this email directly or view it on GitHub
<#14 (comment)
.


Reply to this email directly or view it on GitHub
#14 (comment).

@mattkrick
Copy link
Owner Author

yeaahhh, radium was actually the very first solution for this, but it doesn't meet the reqs above

@patrickleet
Copy link
Contributor

Why the reqs above? I was pretty skeptical but inline styles are working well, and anything that doesn't work with inline, you can just import {Style} - I'll give you read access to my repo if you want to see what I've done? It was based on meatier originally.

@mattkrick
Copy link
Owner Author

The noscript usecase above is a good example. Or consider caching: if you've got a 2MB webpack module and you add 2 pixels to a single margin, you just invalidated the cache on cloudfare & the client. More importantly for me though is that it doesn't really match my style. I don't like bloating my components with extra CSS, but if you do there's nothing stopping you from using radium.

@patrickleet
Copy link
Contributor

https://github.com/kriasoft/isomorphic-style-loader/ for critical path looks promising

@mattkrick
Copy link
Owner Author

@patrickleet i remember that came out around the time i first built meatier. i don't think HOCs & passing CSS down through context is the right way to go about it, but really i haven't any serious thought as to how css should be solved. if you're going to crawl down a react element tree, i figure it may be worthwhile to make something generic & do it for both CSS + data requests (think how relay works now). i should probably get some more react native experience under my belt to see if I could bring over any best practices from there...

@mattkrick
Copy link
Owner Author

@patrickleet i got a meatier-based project that looks like a good candidate for radium. do you happen to have anything opensourced?

@patrickleet
Copy link
Contributor

@mattkrick No not open source - I can definitely post some relevant pieces though.

After going all radium, vs all css modules, I think somewhere in the middle is probably the best balance. Though that was before I saw isomorphic style loader. I haven't had a chance to play with that, but it solves critical path rendering, and then you can load the rest of the styles async.

I originally went with Radium because it handled critical path, and resulted in fast load times, as all the styles were included in the original html doc. Then I made a bigger app, and really, just styles for above the fold should have been inlined. Media queries don't kick in until the js runs, so you can get some weird jankiness on large pages.

When converting css to js, I used css-to-radium https://github.com/FormidableLabs/css-to-radium (it also has a loader, but I found the conversions to not be quite consistent enough)

That being said, here are some files:

EDIT: code here: https://gist.github.com/mattkrick/6fb5d425205ced6633b1

@mattkrick
Copy link
Owner Author

@patrickleet very helpful, thanks! (hope you don't mind i moved the code to a gist).

and then you can load the rest of the styles async.

Could you explain this more? Are you talking about async loading .css files, or just async loading a js module that injects more code into the head.style tags?

What you said is really helpful with media queries, as I was thinking it, but never took the time to make something large enough to test that. So you're saying the page starts to load like a desktop everywhere & then once the css finishes loading, then it jumps into tablet/phone mode?

Did you by chance port something to react-native/electron? One of the selling points (for me) for inline-styles is that it's more inline (punny, sorry) with how native apps do styles. any thoughts?

@patrickleet
Copy link
Contributor

@mattkrick Fine by me - re: async - I haven't done it yet, but a js module that then loads the rest of the CSS.

And yes, but I made it mobile first responsive, so the jump is the opposite direction.

And no, not react native, yet.

I have three meatier apps (micro-apps!) for this project thus far, and have them behind nginx with url rewriting, so they all appear to be at www. What's neat about that is the local storage auth token is saved at www, so they can all access it. Also, they all only have enough CSS for each particular micro-app, which is why critical path and then async loading the rest should work really well.

I'll try to play with the isomorphic style loader in a week or so.

I do have a potential project I may look into react native for in a couple weeks, so would love to see your progress with that.

@mattkrick
Copy link
Owner Author

nice trick on the url rewriting! I gotta remember that when I break up services.

digging more into the radium solution, it seems like there'd still be a bit of work involved when writing styles for a react-native app, so I'm not sure if picking it for the time savings alone would be a good idea.

the isomorphic style loader seems like an OK solution, although I'm not a huge fan of HOCizing every component & passing stuff via context. Eventually i'll get frustrated enough & just make a static component tree so I don't have to... but i'm not there yet.

For now, the async style tag injection seems like the classiest solution + critical path landing page where appropriate. Thanks again for all your input @patrickleet!

@patrickleet
Copy link
Contributor

https://github.com/rofrischmann/react-look/tree/develop/packages/react-look

Just came across this - it seems to be targeting supporting react and react native with two packages that implement the same api.

Haven't dug into it much yet but looks interesting.

@mattkrick
Copy link
Owner Author

@patrickleet great find! 1 API shared across react & react-native could be a game changer... i asked the author about the media-query problem you experienced previously. Even with that, I'm really tempted to give this a go for a cross platform app i got coming up...

@mattkrick
Copy link
Owner Author

the more i play with react-look, the more i like it: http://engineering.khanacademy.org/posts/aphrodite-inline-css.htm

@patrickleet
Copy link
Contributor

Nice - good article. I'm trying to find time to refactor all my radium components to it. I've been pretty uphappy with material ui and radium together as well, and have had to revert to loading css just to customize material ui components.

And the same api as native is awesome. I like the fact aphrodite is decoupled from react though.

Re: media query issue, If the generated classes include generated media queries, I don't think it would be an issue. Haven't dug in enough to know whether or not that's the case though. The delay occurs because the component's state needs to be modified to add a classname. If the classname existed with it's media query rules already I would image the delay would not occur. Same issue that the aphrodite article addressed in constraint 2: "This disqualifies libraries such as Radium which use component state to emulate :hover."

The way Deterministic precedence is addressed seems nice too. I've experienced issues with Radium not applying the correct media query in some states. Though I would assume the order does matter. With radium I'm sharing styles just using {style: Object.assign({}, sharedStyles, { // overwrites }) }, and that has worked reliably.

@patrickleet
Copy link
Contributor

Also, no fallback values in radium has been painful. I saw react-look addresses that as well.

@mattkrick
Copy link
Owner Author

good insight. agreed I'm thinking it's looking like a really nice solution. SSR is still a little clunky as it doesn't support react-dom-stream since it needs a string to do a replace, but I've got a few ideas on how to get around that & maybe the author does, too. I definitely agree with you on the react lock-in issue, but if you're building a web/mobile app on react/native, lock-in is a small price to pay for the HUGE benefit of code reuse.

@mattkrick
Copy link
Owner Author

Close in favor of #141

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

3 participants