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

Working with external css and scss files #3131

Closed
arefaslani opened this issue Oct 19, 2017 · 38 comments · Fixed by #3578
Closed

Working with external css and scss files #3131

arefaslani opened this issue Oct 19, 2017 · 38 comments · Fixed by #3578

Comments

@arefaslani
Copy link

arefaslani commented Oct 19, 2017

It is a redundant issue I know, but I opened this issue intentionally. It's three days I'm setting up a next.js boilerplate(with redux, redux-saga, ...) and it's two days that I'm stucked in setting up loading external css and scss files. I've checked with-global-stylesheet and with-scoped-stylesheet-and-postcss examples, but each of them has major problems mentioned in prior issues. I've seen too many open and closed issues that are solving this problem with hacks... I think it's a good idea that instead of leaving the problem to find the best solution, solve it with current available solutions till find a better one. Because many have this problem now and want to see it solved now!

@CrocoDillon
Copy link

CrocoDillon commented Oct 22, 2017

I agree, only styled-jsx has clean (incl. hot reloading) support and that's what keeping me from using Next.js for anything but when I need some rapid prototyping.

I think the solution to CSS' scoping issues of CSS Modules is a much cleaner one, plus with CSS Modules it's still possible to pass classes to child components (try putting a non-global class on an SVG imported with babel-plugin-inline-react-svg with styled-jsx).

That and I prefer to have standardized .css files to prevent framework lock-in as much as possible and an external CSS files in production for caching (and to make MQ polyfills like Respond.js work if you are unlucky enough to still have to support IE8).

@etodanik
Copy link

Massive +1
It's a huge frustration that such a simple thing as external css/scss is almost impossible to achieve with next.js, which renders it useless for 90% of my applications.

I'm working with bootstrap, and I need a configuration where there will be one global bootstrap css import, with addition of external scoped css.

@ilionic
Copy link

ilionic commented Oct 30, 2017

While we managed to get working external stylus with styled jsx 1 ( webpack for handling compilation ), having hard time figuring it out on styled jsx 2 since breaking change of handling separate css files introduced.
Current approach:

import ComponentStyles from './footer.styl';
...
      <style jsx>
        {ComponentStyles}
      </style>

Would be great to see https://github.com/zeit/next.js/tree/master/examples/with-styled-jsx-scss working with external scss files.

@steeeeee
Copy link

We went through the same ordeal when setting up the environment.
Eventually we settled with a global stylesheet with scss+post css with lost-grid.
Hot reloading works, so while it's not an ideal solution (due to the global stylesheet being loaded all at once), it's an ok compromise.

Dependencies

"autoprefixer": "^7.1.6",
"babel-plugin-module-resolver": "^2.7.1",
"babel-plugin-wrap-in-js": "^1.1.1",
"node-sass": "^4.5.3",
"sass-loader": "^6.0.6",
"pixrem": "^4.0.1",
"postcss-easy-import": "^3.0.0",
"postcss-loader": "^2.0.8"

In package.json

  ...
  "postcss": {
    "plugins": {
      "lost": {},
      "postcss-easy-import": {
        "prefix": "_"
      },
      "autoprefixer": {},
      "pixrem": {}
    }
  }
  ...

In next.config.js

webpack: (config, { dev }) => {
    config.module.rules.push(
      {
        test: /\.(css|scss)/,
        loader: 'emit-file-loader',
        options: {
          name: 'dist/[path][name].[ext]'
        }
      }
    ,
      {
        test: /\.css$/,
        use: ['babel-loader', 'raw-loader', 'postcss-loader']
      }
    ,
      {
        test: /\.s(a|c)ss$/,
        use: ['babel-loader', 'raw-loader', 'postcss-loader',
          { loader: 'sass-loader',
            options: {
              includePaths: ['styles', 'node_modules']
                .map((d) => path.join(__dirname, d))
                .map((g) => glob.sync(g))
                .reduce((a, c) => a.concat(c), [])
            }
          }
        ]
      }
    )
    return config
  }

In pages/_document.js

...

import stylesheet from 'styles/main.scss'

   ...
        <Head>
          <style dangerouslySetInnerHTML={{ __html: stylesheet }} />
        </Head>
   ...

And you can then manage your styles starting from /styles/main.scss
Hope it helps

@jthegedus
Copy link
Contributor

My problem with each of these two style examples ( with-global-stylesheet and with-scoped-stylesheets-and-postcss ) is that none of them are simple to integrate with Jest and Snapshot testing with the CSS in the snapshot. There have been people who successfully get Jest to work with Webpack, but that's by specifically skipping the CSS.

Running a babel-jest preprocessor file as described in this SO answer seems like such a bad hack.

It seems to get external CSS as with-global-stylesheet you must use Webpack, but to use Jest you can't rely on Webpack, only Babel.

Does anyone have ideas in this space?

@anacicconi
Copy link

I'm facing a similar problem. I'm new to nextjs and I can't make the example "with-external-scoped-css" work properly. Sometimes, my css is loaded and sometimes not. I don't know if it's the same issue you're talking about.

#3276

@ilionic
Copy link

ilionic commented Nov 17, 2017

Resolved external styles issues with this loader https://github.com/coox/styled-jsx-css-loader

@arefaslani
Copy link
Author

@ilionic I've checked your solution. It's great! Thank you :)

@etodanik
Copy link

@arefaslani I don't think this issue is closed.

As of HTTP v1 it's still a horrible performance tax for tons of CSS to load, it increases the time to first draw dramatically.

Proper external style support would allow for importing CSS and it resulting in a not an inline <style />...

Also, this suggestion doesn't resolve use cases where a CSS framework like bootstrap needs to be included from node_modules. Including Bootstrap as an inline CSS on every page would be horrible. And would negate the benefits of browser caching, e.t.c

In short, I really don't think this issue can be closed... It's still very much an ongoing problem.

@ilionic
Copy link

ilionic commented Nov 19, 2017

@israelidanny I know what you mean about bootstrap, it's not silver bullet solution. We are extracting core parts of bootstrap framework to separate css file so browsers could cache it. Not ideal, but this awkwardness of dealing with preprocessors and CSS frameworks pushing towards newer css-in-js approaches and cutting dependencies on CSS frameworks.

@etodanik
Copy link

@ilionic I understand what you're saying, but still - having tons of inline CSS is bad for performance (specifically time to first frame). I had quite a few projects where we heavily optimized for that.

It's a shame to lose all that, especially given the fact that we can have server-rendered DOM.

Maybe close this issue, but open a new one, specifically for a solution that lets us link external CSS?
It would be odd to ignore a clearly prevalent development practice.

@ilionic
Copy link

ilionic commented Nov 19, 2017

@israelidanny agree, we are still struggling with CSS performance and without major rewrites not clear how to solve it so indeed this issue goes deeper. Probably styled-jsx is more relevant repo in this case?

@etodanik
Copy link

@ilionic hmm, I don't see how styled-jsx repo would be the right place.

The issue isn't with them, the issue is enabling next.js support of external css files, which may or may not involve anything to do with styled-jsx.

What do you think?

@arefaslani
Copy link
Author

@israelidanny It was not the best solution, but it works anyway. But you're right. Having all the css inline in every page without caching is not best solution. Also I agree with you in this part:

The issue isn't with them, the issue is enabling next.js support of external css files, which may or may not involve anything to do with styled-jsx.

and absolutely disagree with @ilionic in:

Probably styled-jsx is more relevant repo in this case

@P233
Copy link

P233 commented Dec 8, 2017

I managed a solution with styled-jsx/css loader. You can see the code from https://github.com/P233/nextjs-with-scss

However, I noticed the @imported scss files are not watched and won't trigger hot reload, which really makes me frustrated.

@sheerun
Copy link
Contributor

sheerun commented Dec 9, 2017

I've created https://github.com/sheerun/extracted-loader to hotreload extracted stylesheet files. It works pretty nice even for development, so dangerouslySetInnerHTML is not necessary.

You use it as so:

config.module.rules.push({
  test: /\.css$/,
  use: ['extracted-loader'].concat(ExtractTextPlugin.extract({
    /* Your configuration here */
  }))
})

config.plugins.push(new ExtractTextPlugin('index.css'))

@psycura
Copy link

psycura commented Dec 11, 2017

@sheerun , thank you, but how i inject my css into the components?

@sheerun
Copy link
Contributor

sheerun commented Dec 11, 2017

You don't inject, you extract everything with ExtractTextPlugin and write following:

<Head>
  <link rel='stylesheet' type='text/css' href="/static/index.css" />
</Head>

in your components something like

import './styles.scss'

should be enough

@psycura
Copy link

psycura commented Dec 12, 2017

ok, but now i have an error:

./components/Home/Home.js
Module not found: Error: Can't resolve 'css-loader' in 'D:\Sites\work\VisualProposal'
@ ./components/Home/Home.js 13:0-22
@ ./pages?entry
@ multi ./pages?entry

my Home.js component:

import { observer } from 'mobx-react';
import './Home.scss'

import MainSection from './MainSection/MainSection'
import Sections from './Sections/Sections';
import Features from './Features/Features'
import Pricing from './Pricing/Pricing';
import Footer from './Footer/Footer'

const Home = () => {
    return (
        <div className="Home">
            <MainSection/>
            <Sections/>
            <Features/>
            <Pricing/>
            <Footer/>
        </div>
    )
};

export default observer ( Home );

my next.config.js:

const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    webpack: function ( config, { dev } ) {
    
        config.module.rules.push({
            test: /\.(sa|sc|c)ss$/,
            use: ['extracted-loader'].concat(ExtractTextPlugin.extract({
                use: [
                    "babel-loader",
                    {
                        loader: 'css-loader',
                        options: {
                            url: true,
                            minimize: !dev,
                            sourceMap: dev,
                            importLoaders: 2
                        }
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            sourceMap: dev,
                            plugins: [
                                require('autoprefixer')({
                                    /* options */
                                })
                            ]
                        }
                    },
                    {
                        loader: 'sass-loader',
                        options: {
                            sourceMap: dev
                        }
                    }
                ]
            }))
        });
    
        config.plugins.push(new ExtractTextPlugin('index.css'));
        
        if ( config.resolve.alias ) {
            delete config.resolve.alias['react'];
            delete config.resolve.alias['react-dom']
        }
        
        
        
        for (let index = 0; index < config.plugins.length; index += 1) {
            if (config.plugins[index].constructor.name === "UglifyJsPlugin") {
                config.plugins.splice(index, 1, new UglifyJSPlugin({
                    sourceMap: true,
                    parallel: true,
                }));
                break;
            }
        }

        return config
    }
};

So what i`m doing wrong?

@sheerun
Copy link
Contributor

sheerun commented Dec 12, 2017

I've added pretty advanced example with SCSS, url() in stylesheet, img[src]. It should work of of the box with now.sh and has hot-reloading and exporting properly configured:

https://github.com/sheerun/extracted-loader/tree/master/examples/with-next

@psycura I see you use Windows so it might not work for you right away but please send fix PR

@mkozhukharenko
Copy link

mkozhukharenko commented Dec 20, 2017

👎 shame to Nextjs that it does not allow to work with external css files. There a tons of ready-use packages with their own stylsheets which I'd like to use in my project. But I can't! I tried several options but no one really works: spent a day for this already.

@arefaslani
Copy link
Author

@mkozhukharenko It works with css and scss files. I will add an example with https://github.com/coox/styled-jsx-css-loader. It loads external css as scoped or global styled-jsx. For general global scss, I suggest using node-sass and postcss.

@mkozhukharenko
Copy link

@arefaslani I tried all options, neither worked for me. I'm totally disappointed with this framework. How on earth so easy task could be difficult? I will not use such opinionated framework framework again.

@arefaslani
Copy link
Author

@mkozhukharenko Look at this example: https://github.com/arefaslani/next.js/tree/canary/examples/with-external-scoped-scss. I'm creating a pull request to add it to the main repo examples.

@mkozhukharenko
Copy link

just try to add much more css to your file and you will see that styled-jsx can't handle that. I'm getting 'jsx-undefind' cant be cound or 'jsx-4231512' can't be found errors

@protoEvangelion
Copy link

@arefaslani Is there any way to use this pattern with your example?

import classes from './styles.scss'

const MyComponent = () => (
  <header className={classes.header}>Hello</header>
)

@arefaslani
Copy link
Author

@protoEvangelion Unfortunately, currently it works only with styled-jsx.

@protoEvangelion
Copy link

Aww ok thanks for the quick response :)

@slavikshen
Copy link

It's a shame that the build of next.js doesn't think about reusing existing resource at first. What a waste of time setting up complex rules just for a css file.

@timneutkens
Copy link
Member

I'm working on a solution for this issue that allows all webpack loaders to work.

@timneutkens
Copy link
Member

I'd also like to point out that I personally don't like the tone people are having in this issue.
I totally understand you want to import css. And we're very aware of this request. That's why I've spent the past week working on the best possible solution 👍
More on this soon. Till then, please be kind and happy holidays 🎅😄

@imagine10255
Copy link

imagine10255 commented Jan 6, 2018

Hi, 各位, 我在別的 github 發現了一個解決方案,
使用skeleton-loader 遵照BEM規則 達到類似 css-modules 一樣的效果,
因為會需要產生 .scss.json, 之後再使用trash將產生的 .scss.json 刪除
雖然感覺不是很理想, 但給各位發想 看是否有其他更好的方法

Hi, everybody, I found a solution on another github,
Use skeleton-loader to comply with BEM rules to achieve the same effect as css-modules,
Because it will need to generate. Scss.json, then use the trash will be generated. Scss.json delete
Although it feels less than ideal, give it to you to see if there are any other better ways

https://github.com/imagine10255/nextjs-boilerplate/tree/feature/180105/skeleton-scss-module-ben/imagine10255

@Bobeta
Copy link

Bobeta commented Jan 8, 2018

@timneutkens How's it going with your solution?

@arefaslani
Copy link
Author

I have created a Next boilerplate that support importing scss, css and many more features. Take a look at it and if you liked it, give it a star ;)
https://arefaslani.github.io/next-boilerplate

@timneutkens
Copy link
Member

@Bobeta I've got it working, there's a few edge cases I want to fix before submitting a PR.

@Bobeta
Copy link

Bobeta commented Jan 16, 2018

@timneutkens Great to hear! When do you expect it to be published?

@jthegedus
Copy link
Contributor

@Bobeta I'm sure he'll post on this thread when it's published and ready for us to use.

@Bobeta
Copy link

Bobeta commented Jan 16, 2018

@jthegedus I'm sure he will also, but I'm wondering about estimations so that folks can base decisions on that @timneutkens

@lock lock bot locked as resolved and limited conversation to collaborators Jan 30, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.