vercel / next.js

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

Importing correctly scoped css from node_modules outside of _app #12079

Closed mormahr closed 4 years ago

mormahr commented 4 years ago

Bug report

Describe the bug

Importing a stylesheet from a package is not possible in a page, because next throws with this error:

Global CSS cannot be imported from files other than your Custom <App>. Please move all global CSS imports to pages/_app.tsx.
Read more: https://err.sh/next.js/css-global

While I understand where this stems from, it makes code-splitting impossible. If I import a component from a component library, I need to import the CSS as well. There might be libraries that don't correctly scope their selectors, but that shouldn't stop me from overwriting this warning. CSS that is imported from a library isn't inherently "global".

To Reproduce

  1. import "my-library/index.css"
  2. yarn dev
  3. I get the error from above

Expected behavior

The file should be imported.

I see these possible solutions:

Additional context

There have been previous discussions about this.

From the blog

Since stylesheets are global by nature, they must be imported in the Custom component. This is necessary to avoid class name and ordering conflicts for global styles.

I disagree with this statement, the reasoning being that an external library can use CSS modules and package them as a CSS file to import. Which is perfectly valid and common practice and does not have side effects.

10059

This issue got closed because the global import in _app is the correct choice there. This comment describes the exact problem, but there hasn't been any response, as the issue is closed. The comment got a lot of positive reactions though, so I suppose I'm not the only one with this problem.

10975

Seems to be unrelated.

9830

Might be related, but I'm not sure.

My use case

I write long articles with a lot of custom artwork and interactive illustrations. Articles use private npm packages with react components that render SVG with quite a bit of CSS. These packages use CSS modules and export an index.js and index.css. Adding all the CSS files to _app causes all the CSS to be loaded, even if people are on the home page, the contact form, or any other article, even though it's 100% unused. It also goes against having the file system take care of your pages because almost every page corresponds to a CSS import in _app.

softmarshmallow commented 3 years ago

In my case, having this issue due to css files in other packages used with transpile (using yarn workspace)

bchurlinov commented 3 years ago

Did anyone manage to resolve this? Still not working with next 10.0.5.

mfakhrusy commented 3 years ago

I've succeeded in importing css from node_modules by putting it in _app.js (or _app.tsx if you're using typescript) like in the docs over here:

import 'xterm/css/xterm.css'

my nextJS version is 10.0.6

DantSu commented 3 years ago

I have written a webpack configuration to solve this problem :

const removeIssuerApp = issuer => !issuer || !issuer.length ? [] : issuer.filter(i => i.substr(-7) !== '_app.js')

module.exports = {
  webpack (config, options) {
    config.module.rules = config.module.rules.map(
      rule => !rule.oneOf ? rule : ({
        ...rule,
        oneOf: rule.oneOf.map(oneOf => {
          if (!(oneOf.test && oneOf.issuer && (oneOf.test.toString() === '/(?<!\\.module)\\.(scss|sass)$/' || oneOf.test.toString() === '/(?<!\\.module)\\.css$/'))) {
            return oneOf
          }
          const
            and = removeIssuerApp(oneOf.issuer.and),
            or = removeIssuerApp(oneOf.issuer.or),
            not = oneOf.issuer.not || []
          return {
            ...oneOf,
            issuer: !and.length && !or.length && !not.length ? undefined : Object.assign({}, !and.length ? null : {and}, !or.length ? null : {or}, !not.length ? null : {not})
          }
        })
      })
    )
    return config
  }
}
rizkiandrianto commented 3 years ago

I have written a webpack configuration to solve this problem :

const removeIssuerApp = issuer => !issuer || !issuer.length ? [] : issuer.filter(i => i.substr(-7) !== '_app.js')

module.exports = {
  webpack (config, options) {
    config.module.rules = config.module.rules.map(
      rule => !rule.oneOf ? rule : ({
        ...rule,
        oneOf: rule.oneOf.map(oneOf => {
          if (!(oneOf.test && oneOf.issuer && (oneOf.test.toString() === '/(?<!\\.module)\\.(scss|sass)$/' || oneOf.test.toString() === '/(?<!\\.module)\\.css$/'))) {
            return oneOf
          }
          const
            and = removeIssuerApp(oneOf.issuer.and),
            or = removeIssuerApp(oneOf.issuer.or),
            not = oneOf.issuer.not || []
          return {
            ...oneOf,
            issuer: !and.length && !or.length && !not.length ? undefined : Object.assign({}, !and.length ? null : {and}, !or.length ? null : {or}, !not.length ? null : {not},)
          }
        })
      })
    )
    return config
  }
}

Cool ! Thanks for dropping this.

PS: Need to install sass, then it works

sekoyo commented 3 years ago

Is there a technical reason for making such complicated restrictions and webpack rules or is this? I think it should be left to the user as there are some decent arguments for following other conventions like BEM (e.g. easier element selection by third parties). I don't want to break any restrictions in place for something like SSR but at the same time rather than fiddling with the complicated css rules (brittle) I would rather blast them away and do one simple css loader without such discriminations.

Edit: I replaced it with my own simple one and seems to work fine but not well tested yet:

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  reactStrictMode: true,
  webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
    // Find and remove NextJS css rules.
    const cssRulesIdx = config.module.rules.findIndex(r => r.oneOf)
    if (cssRulesIdx === -1) {
      throw new Error('Could not find NextJS CSS rule to overwrite.')
    }
    config.module.rules.splice(cssRulesIdx, 1)

    // Add a simpler rule for global css anywhere.
    config.plugins.push(
      new MiniCssExtractPlugin({
        experimentalUseImportModule: true,
        filename: 'static/css/[contenthash].css',
        chunkFilename: 'static/css/[contenthash].css',
      })
    )

    config.module.rules.push({
      test: /\.css$/i,
      use: !isServer ? ['style-loader', 'css-loader'] : [MiniCssExtractPlugin.loader, 'css-loader'],
    })
    return config
  },
}
timneutkens commented 3 years ago

Thanks for your feedback, I've opened a RFC to change the CSS imports default with some background: #27953

madroneropaulo commented 3 years ago

This is a big issue for me. I'm migrating a big app to next, and I really don't want to translate every scss file into a module. Any workaround?

zigcccc commented 3 years ago

This is a big issue for me. I'm migrating a big app to next, and I really don't want to translate every scss file into a module. Any workaround?

Same here, unfortunately

balazsorban44 commented 2 years ago

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.