mattkrick / meatier

:hamburger: like meteor, but meatier :hamburger:

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

figure out how to split & reference css by chunk

mattkrick opened this issue · comments

this is why inline styles suck.

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.

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

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

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?

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 notifications@github.com
wrote:

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


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

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 notifications@github.com 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 <notifications@github.com
javascript:_e(%7B%7D,'cvml','notifications@github.com');>
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).

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

On Thursday, February 25, 2016, Patrick Scott patrick.lee.scott@gmail.com
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 <notifications@github.com
javascript:_e(%7B%7D,'cvml','notifications@github.com');> 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 notifications@github.com
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).

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

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.

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 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...

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

@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

@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?

@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.

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!

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.

@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...

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

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.

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

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.

Close in favor of #141