facebook / create-react-app

Set up a modern web app by running one command.
https://create-react-app.dev
MIT License
102.28k stars 26.71k forks source link

Support scoped CSS in the same file #5224

Open gaearon opened 5 years ago

gaearon commented 5 years ago

This is kinda vague but I'd like to have a built-in option to write CSS that:

Basically I want "CSS Modules" but without the "Modules" part. Just put it in the same file if that's the only place I use it anyway.

https://github.com/4Catalyzer/astroturf looks related. cc @jquense @markdalgleish

fang0rnz commented 5 years ago

Would this be like opening a style tag in .vue single file components? I think it'd be a great addition

gaearon commented 5 years ago

Also relevant: https://github.com/callstack/linaria

Would this be like opening a style tag in .vue single file components?

Yeah. Except I don't want to partition file into sections like Vue does. Keep it JS but add the ability to add CSS styles inside.

Timer commented 5 years ago

This sounds a bit like styled-jsx.

jquense commented 5 years ago

TBH I think astroturf covers all these points, at it's core it's co-locating CSS files in JS. It's actually implemented as that; the tag content is extracted to a CSS file (not written to disk via webpack magic). The really nice thing about this approach is you get ALL the benefits of writing separate files without the work. For instance mini-extraxt-css-plugin and html-plugin both automatically works without any additional setup.

We've got all the relevant standalone loaders, plugins and Babel plugins in repo, you could integrate with a global css tag with all the library details hidden.

We've been using this for all our large projects with Sass and its really hit a nice sweet spot for convenient component API and playing nice with the tons of existing CSS tooling

jquense commented 5 years ago

I will say css-modules can be wonky and weird but if you're using them, I think the inline approach is really an ergonomic improvement. Cc @ai too

threepointone commented 5 years ago

an astroturf macro would be nice to start playing with this in CRA right now. it appears astroturf has a babel plugin already, so making a macro version shouldn't be too hard?

kdekooter commented 5 years ago

It would be great to treat CSS as a first-class citizen in ReactJS!

ai commented 5 years ago

linaria is a nice project, but right now I prefer astroturf, because of more high order API:

  1. styled to created React component like in styled-component.
  2. Syntax sugar for modifiers: &.primary then can be used as <Button primary>.
  3. Syntax sugar for keyframes. You can write them in the same styled declaration and they still will be scoped.
renatoagds commented 5 years ago

@jquense just a question, looking at astroturf repo I didn't find that. That's an way to create a local class, like :local from css-modules does?

jquense commented 5 years ago

@threepointone

The macro should be easy enough since the core is a babel-plugin, tho i'm not sure how'd deal we the extracted css files, they'd need to be written to disk somewhere. In a webpack env tho the loader avoids this by using a virtual filesystem plugin, exposes the files only to webpack's fs

@renatoagds astroturf is css-modules, so the behavior is the same, we have :local by default on since that's what css-loader does by default, so all classes are scoped. you can use :global to create global ones instead

taion commented 5 years ago

Yup – so the contrast here is that astroturf just handles pulling out the CSS into a separate virtual file, without imposing any of its own opinions on how to do CSS processing. It fully uses the existing CSS processing pipeline, which means that if you have Sass set up, then it will run Sass on your styling code – and this is exactly how we use it.

renatoagds commented 5 years ago

Do we have anyone that use astroturf in production ? To share the pros and cons using it.

taion commented 5 years ago

We use astroturf in production right now.

For the part of the API as described above, there's not really any "experience". The whole point is that it doesn't do anything at runtime – it just pulls the CSS out into a separate file, and lets the rest of your pipeline take over.

The experience analogous to defining a CSS module next to my JavaScript, just without having to go bother with having a separate file.

ai commented 5 years ago

@renatoagds I am using it in production. I use both css and styled function in big React. I converted the project from Emotion.js to astroturf in a few days (mostly because of our auto-import hacks). Animation, many custom PostCSS plugins, webpack CSS minification — everything work. Compare to Emotion we even fix few issues and reduce code size (thanks to &.modifier and @keyframes syntax sugar).

The only problem is that Stylelint doesn’t support it right now. But I created an issue and fix is very simple.

satya164 commented 5 years ago

Linaria covers all of the points,

@ai Linaria supports similar features, has higher order styled API with CSS variables integration and syntax sugar for scoped keyframes. It doesn't support syntax sugar modifiers because we prefer it to keep as close to vanilla CSS as possible and these modifiers can be implemented in userland with a HOC.

In addition to these, Linaria has:

satya164 commented 5 years ago

Regarding the Babel plugin writing files to disk, we used to do that with Linaria but ran into all sorts of problems. For example, if the Babel plugin is co-located with other Babel plugins, you need to have different configs for server, testing, linting etc. to not write files to disk. And if the Babel plugin is just used inside the webpack loader, then there no reason for it to write CSS to disk anyways.

ai commented 5 years ago

@satya164 yeap, I like all types of zero runtime CSS-in-JS =^_^=

giuseppeg commented 5 years ago

I wonder if it is possible to use the current toolchain and write a small babel macro to compile styles with postcss-modules and rewrite the declaration to be compatible with the mini-css-extract-plugin format that in a nutshell is like this https://github.com/giuseppeg/dss/blob/1f183805e790e1721242255cc7d03cfff56d20bd/webpack/loader.js#L19-L24

export.locals is your { class: hashedClass } map. The fact that mini-css-extract-plugin expects the module export to be an array could be problematic though. Maybe @bebraw and @ai can help with validating the idea.

Usage would be like this

import cssModule from 'css-modules/macro'

export default () => <div className={styles.root} />

const styles = cssModules`
  .root { display: block }
`
ai commented 5 years ago

@giuseppeg it is completely what astroturf does 😄 Just a CSS Modules (postcss-modules is the same CSS Modules but for webpack) for Babel with few syntax sugar.

giuseppeg commented 5 years ago

hah cool then! FWIW all the solutions suggested so far (css modules included) have a problem in common that is the cascade. When extracted the css is concatenated in some order so if you import some styles from another module the result is not necessarily predictable.

satya164 commented 5 years ago

@giuseppeg in Linaria, if you use the styled helper instead of passing class names around, it'll ensure that a higher specificity is applied when needed and the result is predictable.

giuseppeg commented 5 years ago

@satya164 cool I guess that with the styled-components like approach it is doable (assuming you are not allowing composition).

everdimension commented 5 years ago

@gaearon

Basically I want "CSS Modules" but without the "Modules" part

Could you please elaborate on that? "Modules" mean that the class names get "scoped", which is what you mention as a desired feature.

I also want to mention that it's great what you're doing here. I fully support you endeavor to implement a fully static css solution!

gaearon commented 5 years ago

I just mean that I don't want two files.

everdimension commented 5 years ago

Oh ok. I can relate to that (kinda 🙂)


I actually wanted to mention something else.

In my opinion, a perfect solution would not only be zero-runtime, but it would also not have any cute syntax shortcuts. E.g. things like this

const styles = css`
  .button {
    color: black;

    &.primary {
      color: blue;
    }
  }
`

seem "nice", but it's not valid css. What if I wanted to use real css nesting from the CSS specification? Granted, nesting is not often needed with "scoped components" model, but let's discuss it for the sake of argument.

It seems to me that I would not be able to do that because it would conflict with the custom syntax of the library. But if the library only supported bare css, then adding nesting would be as easy as adding a postcss plugin. And it would be very similar to how we use babel plugins to enable js features from the spec.

Same goes for other css features as well.

Writing css in a javascript file might feel awkward for those who are not used it and I believe it's important to be able to say the them "well, you can write any valid css here and it will work". That's how CSS modules work. And just being able to have computed values with js template strings is a great addition which is quite easy to grasp.

ai commented 5 years ago

@everdimension yeap, astroturf doesn’t have CSS syntax extensions. It is just PostCSS plugins

taion commented 5 years ago

You technically don't even need to pull in the PostCSS bits for astroturf – it's sufficient to just pull in astroturf/loader and set up css-loader normally, though you probably want to enable modules for things to work the way you want.

I think the delta we want here on astroturf would be something like:

giuseppeg commented 5 years ago

@taion not sure if you can generate and enqueue files in webpack from a loader (babel-loader or astroturf/loader in this case). You'd have to run the pipeline again. If I am right, the only option is to rewrite the file with babel so that a plugin can extract the compiled (scoped) styles to file.

taion commented 5 years ago

@giuseppeg You can do that – it's exactly what astroturf does.

jquense commented 5 years ago

not sure if you can generate and enqueue files in webpack from a loader

You can and that's what it's doing. The only requirement for using astroturf's css tag is that you have a webpack' css-loader with the modules enabled for the inline styles. You don't need Babel or any other compilation. The styled() component sugar is extra

satya164 commented 5 years ago

@giuseppeg in Linaria, we just add require('/path/to/file.css') at the end of the file in the loader and it works great! afaik that's the only way to do it. webpack doesn't support returning multiple files from a loader.

jquense commented 5 years ago

Yeah we change

let styles = css`
  .foo {}
`

To

let styles = require('./foo.module.css')
giuseppeg commented 5 years ago

@jquense is foo.modules.css a virtual file?

giuseppeg commented 5 years ago

indeed it is! nice :) I've always been told to avoid side effects in loaders so I never looked into that. But it obviously makes things easier

satya164 commented 5 years ago

Parcel supports a nice way of doing this where you can return multiple files from the plugin. Unfortunately no such luck in case of webpack :(

Billy- commented 5 years ago

https://www.styled-components.com/ seems to fit the bill?

ai commented 5 years ago

@Billy- styled-components has 15 KB runtime and compile styled every time in the browser. Zero-runtime solutions like astroturf and linaria are much better.

satya164 commented 5 years ago

@Billy- styled-components applies your styles during runtime using the StyleSheet API, linaria and astroturf generate CSS files when you build your app.

wickedev commented 5 years ago

This project was deprecated, but what do you think about this idea? https://github.com/chrisdavies/stylextract-loader

import React from 'react'

export default function ({name}) {
  return (
    <h1 className="hello-header">{name}</h1>
  )
}

<style>
  .hello-header {
    background: steelblue;
    color: white;
  }
</style>
import React from 'react'

export default function ({name}) {
  return (
    <h1 className={css.helloHeader}>{name}</h1>
  )
}

const css = <style>
  .helloHeader {
    background: steelblue;
    color: white;
  }
</style>
ivancuric commented 5 years ago

I'm up for either linaria or astroturf. Both fit the bill of "it's just JS", are compatible with the postCSS ecosystems, don't ship any runtimes, and don't try to hijack the CSS engine.

nckcol commented 5 years ago

I like astroturf's conception of extracting content of css literal as simple CSS module, so you can integrate it in already configured ecosystem that uses css modules. But it would be nice to support dynamic css properties in styled components, like linaria does.

Will be some sort of "extract dynamic properties to style attribute" useful? Or maybe there's some way to pass properties into styles with CSS variables?

ranyefet commented 5 years ago

What is the status currently? Is it possible to use astroturf with CRA without eject?

ai commented 5 years ago

Yeap, astroturf approach is more simple. As result, it could work with different syntaxes like Sass. Because astroturf doesn’t parse it.

Linaria parses styles, so you can use it with more CSS-like syntax. But at least right now, Linaria has more features: source maps and out-of-box Stylelint support. Theoretically, astroturf can do it in future too, but not right now.

amshtemp commented 5 years ago

This change will vastly simplify one of React pain: files organization.

current files structure looks like this:

components
    |-- user
        |-- user.tsx
        |-- user.module.css
        |-- index

I do the above so that I can conveniently import the component from the folder as just:

import User from './components/user';

If this feature get built, we will be able to get ride of folders in components, forever.

I wish @jquense would champion this as he has knowledge about React and build-time CSS-in-JS.

Along with Hooks, it would be awosome to see this built into the next CRA 3.0

Jonnotie commented 5 years ago

Any progress on this? Would love to be able to use Astroturf in CRA since none of the other solutions are ideal.

swyxio commented 5 years ago

i am interested in working on a babel macro for this, however i am quite thrown by this requirement: “Extracted via the same pipeline as CSS Modules”. if i understand it right, that is impossible while keeping it in the same file, or adding some thing early in the build process to generate a css modules file. is that something y’all are open to doing? sounds like a webpack plugin then?

jquense commented 5 years ago

@sw-yx both solutions noted above already have simple Babel plugins and webpack loaders. You can use them as a reference but I think part of the discussion is why bother reinventing the wheel, the devil is in the details

swyxio commented 5 years ago

i see. thanks Jason, only just understood that after rereading again. so basically we're at an impasse of just needing to pick one and implement it? sounds like astroturf fits Dan's original requirement of "potentially use Sass"

brendonco commented 5 years ago

Sound like similar to Zeith static folder?

nrako commented 5 years ago

I've being playing with a mix of proper CSS – you know for the "Cascade Style" part – and scoped embedded styles in component file for both minimal isolated styling and narrow layout tweaking. I've been very happy with this approach – although admittedly I haven't used that into any production code yet.

I've set my preference on styled-jsx as it's well maintained, used extensively and basically all you want for this kind of usage.

There's a plugin for a Sass but although I've used Sass extensively in other projects I'd advocate against it in embedded style. If the embedded scoped styles get so complicate to the point that Sass feels "needed" I'd argue there's another underlying issue and those styles should probably not be scoped, but instead be some normal global cascading style or maybe a CSS module.

I also think it would be better if FE developers would starts to embrace CSS by the specs with the staged features and the process that goes with it (i.e via cssnext) rather than pushing some sugared syntax that newcomers will need to learn on top of the CSS standard. But it seems that this ship already sailed https://github.com/facebook/create-react-app/issues/78 🤷🏽‍♂️😟.

TL;DR; I think I'd welcome styled-jsx support but without Sass and the added completion (maintenance) and futile abstraction for scoped styling which would come with it.