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

[RFC] CSS Support #8626

Closed
Timer opened this issue Sep 4, 2019 · 60 comments
Closed

[RFC] CSS Support #8626

Timer opened this issue Sep 4, 2019 · 60 comments

Comments

@Timer
Copy link
Member

Timer commented Sep 4, 2019

Goals

  • Support global CSS effects (e.g. Bootstrap, Normalize.css, UX-provided, etc)
  • Support stable component-level CSS
  • Hot-reload style changes in development (no page refresh or state loss)
  • Able to code-split for critical CSS extraction in production
  • Leverage existing convention users are already familiar with (e.g. Create React App)
  • Retain existing styled-jsx support, potentially more optimized

Background

Importing CSS into JavaScript applications is a practice popularized by modern day bundlers like Webpack.

However, it's tricky to get CSS imports right: unlike JavaScript, all CSS is globally scoped. This means it does not lend itself well to bundling. Especially bundling between multiple pages where styling is separated in a CSS file per page and load order matters.

By our measurements, more than 50% of Next.js users use Webpack to bundle .css files, either through @zeit/next-css, @zeit/next-sass, @zeit/next-less, or a custom setup. This is further verified through talking to companies. Some have all their styles through importing CSS, others use it for global styles and then use a CSS-in-JS solution to style components.

Currently we have these three plugins that allow you to import CSS, however, they have common issue in CSS ordering and certain loading issues. The reason for this is that @zeit/next-css doesn't enforce a convention for global styles. Global CSS can be imported in every component / JavaScript file in the project.

To solve these common issues and make it easier for users to import CSS we're planning to introduce built-in support for CSS imports. This will be similar to styled-jsx where if you don't use the feature there will be no built-time or runtime overhead.

This effort also allows us to improve developer experience of the solution.

Proposal

Next.js should support modern CSS and down-level it on behalf of the user. This approach would be similar to how we support modern JavaScript and compile it down to ES5.

By default, we should:

During development, CSS edits should be automatically applied, similar to JavaScript hot reloading.

In production, all CSS should be fully extracted from the JavaScript bundle(s) and emitted into .css files.

Furthermore, this .css should be code-split (when possible) so that only the critical-path CSS is downloaded on page load.

Global CSS

Next.js will only allow you to import Global CSS within a custom pages/_app.js.

This is a very important distinction (and a design flaw in other frameworks), as allowing Global CSS to be imported anywhere in your application means nothing could be code-split due to the cascading nature of CSS.

This is an intentional constraint. Because when global CSS is imported in for example, a component, it will behave different when moving from one page to another.

Usage Example

/* styles.css */
.red-text {
  color: red;
}
// pages/_app.js
import '../styles.css'

export default () => <p className="red-text">Hello, with red text!</p>

Component-level CSS

Next.js will allow pure CSS Modules to be used anywhere in your application.

Component-level CSS must follow the convention of naming the CSS file .module.css to indicate its intention to be used with the CSS Modules specification.

The :global() CSS Modules specifier is allowed when combined with a local class name, e.g. .foo :global(.bar) { ... }.

Usage Example

/* components/button.module.css */
.btnLarge {
  padding: 2rem 1rem;
  font-size: 1.25rem;
}

.foo :global(.bar) {
  /* this is allowed as an escape hatch */
  /* (useful when controlling 3rd party libraries) */
}

:global(.evil) {
  /* this is not allowed */
}
/* components/button.js */
import { btnLarge } from './button.module.css'

// import '../styles.css'; // <-- this would be an error

export function Button({ large = false, children }) {
  return (
    <button
      className={
        large
          ? // Imported from `button.module.css`: a unique class name (string).
            btnLarge
          : ''
      }
    >
      {children}
    </button>
  )
}
@Timer Timer added the RFC label Sep 4, 2019
@alejalapeno
Copy link
Contributor

I'm guessing with plugins like @zeit/next-sass already working with the current supported flavor of CSS Modules that .module.scss would also work? Or does the phrase "pure CSS Modules" mean only .module.css?

Sass support is make it or break it for most projects to me.

@timneutkens
Copy link
Member

timneutkens commented Sep 4, 2019

The @zeit/next-css/sass/less/stylus plugins will be deprecated in favor of this RFC (built-in support). We're still discussing to what extent sass will be part of the core vs a plugin. My current thinking is when you add node-sass it'll enable sass support. Similar to what we do for TypeScript. The reason we won't add it as a dependency is that it's quite large and very slow to install because of native bindings etc.

@zen0wu
Copy link

zen0wu commented Sep 5, 2019

This is exciting! I was tripped by conflicting css class names just as today!

Question, how does it work with TypeScript? One reason that I haven't enabled css-module is that, importing css module would give an any object (maybe a bit magical, like .foo-bar will be become a field called fooBar?), which is not TS friendly. Since Next is now TS by default, I would imagine we can do better in this matter?

@TheHolyWaffle
Copy link

I'm not sure if "Automatically fix known flexbugs for the user" is going to be a good idea.

I fear that we'll end in confusing scenarios where third party component libraries on npm, that include css stylesheets, will behave differently in nextjs environments than in others (such as create-react-app).

@Timer
Copy link
Member Author

Timer commented Sep 5, 2019

@TheHolyWaffle fixing known flexbugs creates a more predictable behavior -- and is also what Create React App does.

@Timer
Copy link
Member Author

Timer commented Sep 5, 2019

@ZenoZen Per the CSS Modules specification, it does not do anything fancy to camelcase for you. If you use class names with a dash (class-name) you'd need to explicitly access that on the object.

e.g.

import Everything from './file.module.css'

// later on

Everything['class-name']

As for TS, we don't plan any magic to handle the types but it's pretty easy to define a TS type to handle these files.

declare module '*.module.css' {
  const classes: { readonly [key: string]: string };
  export default classes;
}

declare module '*.module.scss' {
  const classes: { readonly [key: string]: string };
  export default classes;
}

declare module '*.module.sass' {
  const classes: { readonly [key: string]: string };
  export default classes;
}

@adamwathan
Copy link
Contributor

Whatever we do here can we make sure it's still easy to use custom PostCSS plugins like it is now with @zeit/next-css? It would be a real shame if we lost support for that — CRA doesn't support this and because of that it's impossible to use tools like Tailwind CSS in any sort of reasonable way.

I would be careful about these three features in this regard:

  • Use Autoprefixer to add vendor-specific prefixes automatically (without legacy flexbox support)
  • Polyfill or compile Stage 3+ CSS features with their equivalents
  • Automatically fix known "flexbugs" for the user

...because this is all done with PostCSS, and it's very important that PostCSS plugin order be under the end-user's control.

I would suggest that if you are going to enable those three plugins by default, that the user should be able to completely override the default PostCSS config by providing their own postcss.config.js file. They would have to add those plugins manually if they went this route, but that level of control is necessary in my opinion.

TL;DR please be careful not to break the ability for users to have complete control of PostCSS, I literally can't use CRA because of this and I would be very sad if I couldn't use Next anymore.

@jescalan
Copy link
Contributor

jescalan commented Sep 5, 2019

Seconding @adamwathan - would be great to get more detail here on how postcss option customization would look. It would be really great to have an option to modify postcss config via next plugin as well as postcss.config.js, so that configs shared across multiple projects can be easily injected without an extra file.

@felixmosh
Copy link
Contributor

I think that it is better to enable css modules for all css imports (not only for .module suffix), and the one that we want to be global we will need to specify a .global suffix.

@jamsch
Copy link

jamsch commented Sep 5, 2019

CSS Modules would be great. I think keeping the *.module.(css|scss) spec is fine as it does align with create-react-app, allow for typescript typings, and allow for easier migrations for other users new to Next.js

With typescript, you can enforce CSS module typings by generating *.d.ts typings for every *.(css|scss) file using typed-css-modules and typed-scss-modules but if you don't want to do that and still want autocompletion, you can use the following VS Code extension:
https://marketplace.visualstudio.com/items?itemName=clinyong.vscode-css-modules

@Timer
Copy link
Member Author

Timer commented Sep 6, 2019

@adamwathan rest assured we'll be allowing PostCSS configuration! We're not sure how the exact implementation will look like yet, but it should emerge naturally with the CSS pull request.

@andreisoare
Copy link

This is great!! Is there a timeline for this? Is it happening this year?

@Timer
Copy link
Member Author

Timer commented Sep 13, 2019

@andreisoare we're about to merge the global half of this RFC: #8710

We'll be continuing on to the CSS Modules support ASAP!

@Pushplaybang
Copy link

@adamwathan @Timer would be great if we could continue to use the conventional postcss.config.js file in the root of the project, as we can now with next-sass.

So glad to see this coming to core.

@bySabi
Copy link

bySabi commented Dec 18, 2019

I know that CSS Modules does not establish any convention on the extension of the files but how the modules are imported.

Where I want to go is that .module.css is the conventions chosen by the Next.js Teams and they should "facilitate" the possibility that some users can change it even if it is via a post-install module that doesmonkey patching

I think that .module.css is the first naming convention | extension imposed by Next.Js. Please correct me if I'm wrong.

@timneutkens
Copy link
Member

timneutkens commented Dec 18, 2019

The choice of the module.css convention collides with the convent already established by css-modules/css-modules
Examples here:
create-react-app.dev/docs/adding-a-css-modules-stylesheet
and here:
gatsbyjs.org/docs/css-modules

Yes, probably making the extension configurable could complicate it more than necessary. But at least you could define .module.css in a constant file and not hard-coded in several places to change it via monkey patching

You're contradicting your point here, .module.css is being used by create-react-app and gatsby for the same feature. It's an established pattern.

changing the naming convention however is not an established pattern and there's no reason for allowing it in Next.js.

@timneutkens
Copy link
Member

I think that .module.css is the first naming convention | extension imposed by Next.Js. Please correct me if I'm wrong.

Next.js establishes conventions over configuration options, eg:

  • pages directory being routes
  • pages/api being API routes
  • support for .js .tsx .tsx

@bySabi
Copy link

bySabi commented Dec 18, 2019

Hi @timneutkens

The pattern is in how CSS Modules establishes how CSS files should be imported into JS objects, the name of the CSS in question is marginal. In that regard Next.js is not using that particular pattern with the new implementation.
The CSS Modules as in Gatsby and CRA were already implemented in Next before: https://github.com/zeit/next-plugins/tree/master/packages/next-css

I don't question the convention used, .module.css, even knowing that I could confuse CRA and Gatsby users with CSS Modules. Choosing a suitable name is difficult and not everyone is always happy.

Although I believe that the .m.css convention is better because it is shorter (more suitable for an extension) and less redundant (import statement is for importing modules) I would like you to take this into account. Defining conventions in general and .module.css in specific in a constant file would help enough to patch them.

Anyway, many congratulations on the CSS modules. You have done a magnificent job, it seemed an "almost" impossible task.

@LasaleFamine
Copy link

If I can add my two cents on naming convention: we have a components library that uses the .pcss extension since we need PostCSS for compilation and we decided to use this naming convention, also we need to import these CSS as module. How would you address this case? We are currently hacking the webpack configuration and patching the test regex of the loaders, but it feels dirty as you may imagine.

@bySabi
Copy link

bySabi commented Dec 18, 2019

I imagine that .module.css supported postCSS just as global .css currently do.

@bySabi
Copy link

bySabi commented Dec 18, 2019

I know what happens, I think I need a vacation.

When reviewing the PR: #9686 I thought I "saw" that CSS modules were imported scoped just as global modules are imported.

I sincerely thought I read this code:

index.module.css

.redText {
  color: net;
}
import './index.module.css'

export default function Home () {
  return (
    <div id = "verify-red" className = "redText">
      This text should be red.
    </div>
  )
}

Obviously nothing to do with reality. In reality the real code is this.

import {redText} from './index.module.css'

export default function Home () {
  return (
    <div id = "verify-red" className = {redText}>
      This text should be red.
    </div>
  )
}

Just CSS Modules as in CRA or Gatsby. Hence the confusion, my confusion.

I apologize to everyone for the mistake.

:-(

@xyy94813
Copy link

When use CSS Modules, the main problem is that:

Use CSS Modules in my project, and some third-party components use CSS Modules too.
But... some third-party components use global CSS.

I think the solution of .module.css extension is easy to understand and implement.
And the other frameworks (CRA、Gatsby) has reached a consensus.

Until now, I haven't encountered any problems about the extension solution.
So, i hope promote the development of .module.css extension.

If there are another solution about the CSS Modules problem, it is better.

@bySabi
Although .m.css is shorter, but i do not think it is better.
Is it min.css or module.css?

@felixmosh
Copy link
Contributor

@xyy94813 or the exact opposite, all css imports are modules, files with .global.scss will be global ones

@xyy94813
Copy link

@felixmosh
But, It is unfriendly to published components.

@ghost
Copy link

ghost commented Dec 19, 2019

I think the convention is a good default, but that some configuration options specific to CSS modules should eventually be made available, for example those which are passed to css-loader, as some users will want control over how the "scoped" class names are output. An option could be added to supply a regex which determines which CSS files are loaded as "modules"

@timneutkens
Copy link
Member

timneutkens commented Dec 19, 2019

as some users will want control over how the "scoped" class names are output

Examples of why you'd want that? I can't think of any.

@ghost
Copy link

ghost commented Dec 19, 2019

Examples if why you'd want that? I can't think of any.

@timneutkens Well I might prefer to output a naming pattern that conforms basically to the naming pattern of my global classes with only the scoped string at the end.
Beyond this, I was thinking of post-build purging or splitting or loading scenarios where I might want to whitelist a certain namespace, but I haven't worked that out yet

@alejalapeno
Copy link
Contributor

I think it's important to remember that an opinionated framework is meant to make decisions for you in order to eliminate initial setup and enforce uniformity.

Some of those decisions should come with defaults that can be changed through configuration options or extensions, but with everything you make customizable you increase the codebase which creates more code to test, maintain, and document which in turn needs to be maintained as well.

Something like scoped output hash pattern is so granular if you get to a point where you need to overwrite the default it's likely you're at the point where you should just be writing the custom loader you need yourself.

@gragland
Copy link

gragland commented Dec 19, 2019

When working with new React devs I've always found it much easier to convey the concept of scoping styles to a component via className. Just give the outer element in your component a unique className, usually the same as your component name, and write your style like this if using SCSS:

.Navbar {
  .logo { ... }
  .nav-item { ... }
}

I definitely see the benefits of css-modules, but I'm a bit worried that by no longer supporting the above method Next.js would end up becoming less friendly to new devs. Maybe that's a fine tradeoff to make if you're seeing a lot of people running into css ordering issues, but I wonder if enforcing css-modules could at least be disabled via next.config.js. Or maybe we could even have a build-time check that ensures all styles are properly scoped with a unique className, although not sure how doable that is technically.

@ghost
Copy link

ghost commented Dec 20, 2019

I'm a bit worried that by no longer supporting the above method Next.js would end up becoming less friendly to new devs

Unless I misunderstood something, I don't think the CSS Modules thing is exclusive, rather the loader would apply CSS Modules' parser only if the file you import has the *.module.css naming pattern, or *.module.scss for that matter

@alejalapeno
Copy link
Contributor

@lunelson-bl

It is semi-exclusive as opposed to how CRA or Gatsby enable the parser only when you use the naming pattern Next would disallow non-module importing directly into the component JS.

From the RFC:

/* components/button.js */
import { btnLarge } from './button.module.css'

// import '../styles.css'; // <-- this would be an error

In order to use non-module classes you would need to import the stylesheet into your global stylesheet that gets imported only into _app.js (if you wanted them split and colocated with your component files). Otherwise they'd need to be placed directly into that global stylesheet.

@poyiding
Copy link

poyiding commented Dec 24, 2019

I am a new user of next.js. After I read this issue and feel a little confused. I think the if the next.js support build-in-css-support , so we don't need @Next-css plugin, but it doesn't work and throw error about "ModuleParseError".
If i adding in the @Next-css plugin to add CSS imports support, it works, so how build-in-css-support without @Next-css , can you provide an example?
thanks @ALL.

@erkanunluturk
Copy link

Ben next.js'nin yeni bir kullanıcısıyım. Bu sorunu okuduktan ve biraz kafam karıştı. Ben next.js destek build-css desteği varsa düşünüyorum , bu yüzden @ next-css eklentisine ihtiyacımız yok, ancak işe yaramıyor ve "ModuleParseError" hakkında hata atmıyor.
CSS içe aktarma desteği eklemek için @ next-css eklentisine eklersem, çalışır, bu yüzden @ next-css olmadan nasıl css-build oluşturursanız, bir örnek verebilir misiniz?
teşekkürler @ALL .

add a config to your next.config.js like this:

module.exports = {
  experimental: {
    css: true
  }
}

@poyiding
Copy link

poyiding commented Dec 24, 2019

@erkanunluturk thanks, it's ok, but has a experimental feature warning, does it matter ?
And how is it compatible with antd? I know with-ant-design is ok.

@Timer can you provide an example import ant-design use [RFC] css support? thanks.

@ducanh2110
Copy link

ducanh2110 commented Dec 25, 2019

I still have problem with Router.push("/") when I am in another link. Anyone has solution for it? Please help me thanks so much

@StarpTech
Copy link
Contributor

⚠️ Please stop using this thread as wishing well and issue tracker. This is an RFC and they are still implementing it.

The last meaningful comment of the team is #8626 (comment) You can evaluate the current implementation by enabling it via #8626 (comment)

I'm missing the possibility to track the work. How can we help? @Timer is it possible to connect this RFC with milestones, a roadmap? What's done from the points in the RFC?

No idea, how to keep on track 😕

@Timer
Copy link
Member Author

Timer commented Dec 25, 2019

@StarpTech this RFC is completely implemented; Global CSS and CSS Modules are working per the RFC description.

You can test both with the single experimental flag!

I'll lock this thread to prevent further notification sprawl. If anyone identifies issues, please open a new issue and let us know! We'll post here when the support is officially marked stable (and is on by default).

@vercel vercel locked as off-topic and limited conversation to collaborators Dec 25, 2019
@timneutkens
Copy link
Member

Landed in 9.2: https://nextjs.org/blog/next-9-2

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests