vercel / next.js

The React Framework
https://nextjs.org
MIT License
122.19k stars 26.16k forks source link

Importing CSS files? #544

Closed matthewmueller closed 7 years ago

matthewmueller commented 7 years ago

Sometimes it's nice to break out your CSS into a separate .css file. I've tried to do the following:

pages/
└── index
    ├── index.css
    ├── index.js
    └── component.js

Then in the index.js, I've tried to do:

import css from './index.css'

And in next.config.js:

module.exports = {
  webpack: function (config) {
    config.module.loaders = (config.module.loaders || []).concat({
      test: /\.css$/, loader: 'raw'
    })
    return config
  }
}

But unfortunately, it keeps giving me:

 ERROR  Failed to compile with 1 errors

This dependency was not found in node_modules:

* ./index.css

Seems like it's not resolving to the right place for some reason, the local component.js works though via import component from './component.js', so I'm not sure what's going on here.

nkzawa commented 7 years ago

You mean this doesn't work on server ? I think we don't have workaround for this yet. cc @arunoda

matthewmueller commented 7 years ago

oh right, i guess there'd need to be a way for the webpack config in next.config.js to work on both sides in order for this to work.

MikeDigitize commented 7 years ago

I've got a kind of ballpark similar issue in that I just can't seem to get Next to play nicely with CSS or SASS. I have a components directory with standard React components in that I import into my pages but whenever I attempt to import in SASS (or CSS) files I get a ~"you need to use the appropriate loader for this file" type error message.

Generally in React I import SASS files and have Webpack compile using style, css and sass loaders. I've attempted to add these to the next.config.js file (and NPM installed them) but still get the same error message.

My next.config.js file:

module.exports = {
  webpack: (config, { dev }) => {
    config.module.rules.push({ test: /\.scss$/, loader: ['style-loader', 'css-loader', 'sass-loader'] });
    return config;
  }
}

Sorry if these questions sound dumb or I've missed something obvious in the docs that spell out the answers to them, but if anyone has a working example of importing / compiling SASS (or at the least CSS) into a component or a page with whatever's needed to add to the next.config.js to load / compile them, I'd massively appreciate it. Thanks!

spacedragon commented 7 years ago

I'm using css-modules-require-hook to make css work.

kpuputti commented 7 years ago

@spacedragon Do you have an example how to integrate css-modules-require-hook with Next.js? I'm having problems with getting it working.

MikeDigitize commented 7 years ago

I'm still having issues with getting SASS to compile if anyone could shed some light on how to do this or just import a CSS file within Next it would be appreciated (via a code example).

MikeDigitize commented 7 years ago

Interesting that the README file has been updated to remove the SVG loader example and changed to say adding loaders for file like SVG, CSS and SASS is discouraged. I'm not sure why inlined CSS is OK but imported CSS isn't but I'm sure there's a good reason why. I'm just currently uncertain about the best strategy to handle none JS defined / inlined CSS and SASS.

kpuputti commented 7 years ago

@MikeDigitize See comment on #627 and #638 .

viktorbezdek commented 7 years ago

It's actually possible and pretty easy to process styles on server side.

directly in node:

require.extensions['.css'] = function(file) {
    console.log(file.id)
    return;
}

via babel register:

// from https://babeljs.io/docs/core-packages/babel-register/
require("babel-register")({
  // Optional ignore regex - if any filenames **do** match this regex then they
  // aren't compiled.
  ignore: /regex/,

  // Ignore can also be specified as a function.
  ignore: function(filename) {
    if (filename === '/path/to/es6-file.js') {
      return false;
    } else {
      return true;
    }
  },

  // Optional only regex - if any filenames **don't** match this regex then they
  // aren't compiled
  only: /my_es6_folder/,

  // Setting this will remove the currently hooked extensions of .es6, `.es`, `.jsx`
  // and .js so you'll have to add them back if you want them to be used again.
  extensions: [".es6", ".es", ".jsx", ".js"]
});

via webpack loaders:

I personally use isomorphic-style-loader as it allows me to inline critical CSS while rendering on server. Hot reloading and other DX related stuff works too. I'm not really fan of CSS in JS as it complicates using 3rd party components and and somehow takes away the C from CSS.

noeljackson commented 7 years ago

@viktorbezdek Have you successfully used isomorphic-style-loader with next.js?

viktorbezdek commented 7 years ago

@noeljackson Not really, but I intend to. Next.js looks promising and could save me lot of time if I make it work. Will look into it in next one or two weeks and submit pull request if I'm successful.

noeljackson commented 7 years ago

@viktorbezdek I will put a bounty on this one, as it's really crucial to a project I'm working on. I know I'm not inept, but I do not understand how to debug the babel transformations enough to figure this out. I've tried permutation of these ideas and nothing works 100%. I have been able to get raw-loader to pull in a data-encoded stylesheet using babel-plugin-webpack-loaders, but none of the style loaders work. Thanks for chiming in! :) Much appreciated.

pencilcheck commented 7 years ago

Is there a solution to this? I would like to see a solution so I don't have to include css globally.

matthewmueller commented 7 years ago

FWIW, I've just been putting my CSS files in the /static folder. Not great colocation, but not a huge deal either.

viktorbezdek commented 7 years ago

There will be a solution. I just didn't manage to finish it. I've locally very first prototype which seems to work, however it needs few hours to be finished. I'm pretty sure I'll be done after weekend. Stay tuned.

viktorbezdek commented 7 years ago

@matthewmueller You are using CSS modules?

abelovic commented 7 years ago

@viktorbezdek Thanks for working on this! CSS Modules support (or similar) is important for this project IMO. Styled jsx, is ok for simple situations but is hard to read for heavily styled components.

Would a babel plugin like this be an option (server side as well)? https://github.com/gajus/babel-plugin-react-css-modules

I tried to get this to work but but no luck :/

stephenmathieson commented 7 years ago

I got CSS modules sort of working with babel-plugin-css-modules-transform. See my hacky example in my fork.

The downside is you have to kill the server & restart it every time you make a change to the CSS.

mquandalle commented 7 years ago

Some React components expose default styling though a importable static resource. For instance to import the default style of https://github.com/react-component/slider one would use:

import 'rc-slider/assets/index.css';

It sure is possible to copy paste this stylesheet in the static/ directory but it won't stay in sync with the upstream style on a future component update, and it doesn't match what this component documentation recommend.

rauchg commented 7 years ago

The problem is that those CSS files introduce global effects. We need to be able to capture the CSS and put it inside the React lifecycle, so that it gets unmounted, server-rendered, etc.

Many libraries do that, but I don't think it's a good pattern.

mquandalle commented 7 years ago

I'm not familiar with Zeit Next internals, but could some static analysis of the import be used to register/capture the CSS?

rauchg commented 7 years ago

We could, but it would be really strange. Similar to something that's outside of render() magically inserting itself inside your component lifecycle.

auser commented 7 years ago

// Figured I'd share this for anyone else

Well... I just spent a bit too much time trying to hack CSS in here, BUT I landed on a solution that works (for me). Admittedly, it's a hack, but hot reloading works and so does server-side building.

Using (shudder) gulp, with this gulpfile (https://gist.github.com/auser/25e88e39d83413773c01f4c000ec2806) all **/*.scss files get concatenated together and shoved into a Styles component which I mount on the page as a "normal" element.

Hope this helps someone else until we can get true postcss support in next.

brycejacobs commented 7 years ago

Thanks for the hack @auser, I've been looking at the webpack config all day with no luck!

Edit: Btw, you need to add a sass parser to the gulpfile!

auser commented 7 years ago

Yes and no... I just use the .scss extension as a way to differentiate pure css files from pre-compiled ones. Since postcss (with precss) mimics sass well enough, I don't have one. Feel free to edit for yourself with a sass parser.

ghost commented 7 years ago

Seems like that is currently the best solution, using gulp to compile css file and build it inline or just even in /static if you don't mind not having hot reloading.

davibe commented 7 years ago

I got css import + hot reload working in a clean way. The css is imported as string and the user can inline it in the page just like any other string. Please have a look at this example, help me test and PRs are welcome!

https://github.com/davibe/next.js-css-global-style-test

I believe this example should make it into next.js official examples. Does it? @rauchg @arunoda @nkzawa (sorry if i tagged someone who's not directly involved)

iamstarkov commented 7 years ago

@davibe thanks for your demo and babel-plugin-wrap-in-js

khrome83 commented 7 years ago

In the example I see use of a CSS file, and a SCSS file. Do you know if this would work with postcss & cssnext?

davibe commented 7 years ago

@khrome83 I don't see why not, I think its just a matter of adjusting .babelrc and next.config.js

tgoldenberg commented 7 years ago

@davibe I found that I was unable to deploy my app based on your configuration. The build was unable to read next/babel in the .babelrc file. I submitted an issue, but I'm really hopeful that a solution emerges from all this. Missing the ease of import file.css from create-react-app, but I know there must be a solution coming :)

rauchg commented 7 years ago

The solution I want is likely along these lines:

https://github.com/zeit/styled-jsx/pull/100#issuecomment-277133969

We might support importing .css (by means of simply transpiling it into a module that exports a string) (and likewise we might support .svg by transpiling it into a pure react component)

stephenmathieson commented 7 years ago

and likewise we might support .svg by transpiling it into a pure react component

This is a pretty simple trick. I'll create a basic example showing how I handled this in another project =)

EDIT: see https://github.com/zeit/next.js/pull/982

satazor commented 7 years ago

Based on @davibe sample I've created https://github.com/moxystudio/next.js-style-loader that will hopefully ease out adding css files into next.js projects. It's similar to webpack's style-loader in that it will add/remove stylesheets as the user navigates. It also supports SSR.

It does work well with css-loader (with and without css modules), postcss-loader, sass-loader and possibly others. Note that when css-loader is used, its url option must be set to false beucase next.js images, fonts, etc must live /static. You will find all this information in the README.

Enjoy and please give me feedback!

pencilcheck commented 7 years ago

Thanks for the repo! It worked for importing the css files. I am trying blueprintjs and it seems like the css is loading correctly! However the @font-face rule that the css is including doesn't seems to work. :\

--------------------edit----------------------

It is actually working, my bad! However the icons are not being loaded because nextjs routing by default doesn't allow serving static content outside /static/ and relative path actually cause it to load with path that is not permitted.

satazor commented 7 years ago

@pencilcheck yes you must use paths pointing to /static, perhaps I will make that more clear in the README.

pencilcheck commented 7 years ago

Is there any workaround regarding relative path included in the css files such as fonts atm? Or I simply have to copy the whole font files and css into static folder in order for it to work?

satazor commented 7 years ago

@pencilcheck the CSS files can stay outside of static. Your images and fonts must be inside static and you reference them with /static/file.

pencilcheck commented 7 years ago

I get it. However I'm using blueprint, which is an npm package, I would like to be able to not have to modify any files inside node_modules.

satazor commented 7 years ago

@pencilcheck That's not possible unfortunately. next.js is very strict in how it handles images and other assets. Lets not pollute this conversation and please create an issue in next-style-loader repo if you may.

davibe commented 7 years ago

@tgoldenberg can you describe the problem better or tell me how to reproduce ? please refer to my repository. It's easyer for me to track issues there.

tgoldenberg commented 7 years ago

@davibe, it ended up being a problem using yarn over npm install. Yarn was throwing some inexplicable errors, but once I removed it the example worked fine in production.

thomaslindstrom commented 7 years ago

I just spent 4 hours trying to set this up and thought it might be of interest for anybody looking to save some time. It applies styles automatically on change (just like in the current example), runs the CSS through PostCSS and gives you local module names from css-loader. The lack of the latter were major deal breakers for me in the current state of the “global css”/“css imports” examples.

component.js

import React from 'react';
import stylesheet from './styles.css';

const [css, className] = stylesheet;
const Component = () => (
    <p className={className.paragraph}>
        <Head><style dangerouslySetInnerHTML={{__html: css}} /></Head>
        bazinga
    </p>
);

.babelrc

{
    "presets": [
        "next/babel"
    ],
    "plugins": [
        ["wrap-in-js", {
            "extensions": ["css$"]
        }]
    ]
}

next.config.js Notice the amazing hack with exports-loader. There's got to be a better way, surely???

module.exports = {
    webpack: (config) => {
        config.module.rules.push(
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'emit-file-loader',
                        options: {
                            name: 'dist/[path][name].[ext]'
                        }
                    },
                    {
                        loader: 'raw-loader'
                    },
                    {
                        loader: 'val-loader'
                    },
                    {
                        loader: 'exports-loader',
                        options: {
                            0: 'exports[0][1]',
                            1: 'exports.locals'
                        }
                    },
                    {
                        loader: 'css-loader',
                        options: {
                            modules: true,
                            minimize: true
                        }
                    },
                    {
                        loader: 'postcss-loader'
                    }
                ]
            }
        );

        return config;
    }
};
jozanza commented 7 years ago

I came up with a solution myself, which is very similar to what @satazor posted higher up in the thread: https://github.com/jozanza/next-css-json-loader.

Just add a few lines to your next.config.js:

module.exports = {
  webpack: config => {
    config.module.rules.push({
      test: /\.css$/,
      loader: 'emit-file-loader',
      options: {
        name: 'dist/[path][name].[ext]',
      }
    }, {
      test: /\.css$/,
      loader: 'babel-loader!next-css-json-loader',
    });
    return config;
  },
};

Styles are imported as js objects so it's very easy to use with glamor and similar solutions:

// .css files now conveniently expose all styles as js objects
import styles from 'some-package/styles.css';
import { css } from 'glamor';
// ...
<div {...css(styles)}>
  this is a nice box. 
</div>

Cheers! 🍻 :)

kristojorg commented 7 years ago

Is there a way to make this work for importing markdown files as strings? I am currently using raw-loader, but as it is a webpack plugin it doesn't work on the server.

khrome83 commented 7 years ago

@kristojorg

I just wrote a babel plugin for markdown import. On my mobile right now, but if you view my GitHub you will see it.

rauchg commented 7 years ago

@khrome83 that sounds awesome. Looking forward to trying it out

kristojorg commented 7 years ago

Thank you @khrome83! I'll give it a shot

khrome83 commented 7 years ago

I had to do this really quickly, so I did not update the readme. But you just include it as a babel plugin, and then use

import File from 'File.md';

kristojorg commented 7 years ago

I got it to work, thank you !! Very helpful