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

Importing correctly scoped css from node_modules outside of _app #12079

Closed
mormahr opened this issue Apr 21, 2020 · 60 comments · Fixed by #16993
Closed

Importing correctly scoped css from node_modules outside of _app #12079

mormahr opened this issue Apr 21, 2020 · 60 comments · Fixed by #16993
Assignees
Milestone

Comments

@mormahr
Copy link

mormahr commented Apr 21, 2020

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:

  • Global flag in next.config.js
  • Comment annotation on import
  • Warn instead of completely blocking it
  • Check if the CSS only contains scoped selectors

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.

@mikestopcontinues
Copy link

I'm facing this same problem simply trying to use Linaria, which scopes its own class names. Though the css files it produces don't end in .module.css, they're "modules." I need an easy way to integrate with the library.

@x5engine

This comment has been minimized.

@salazarm
Copy link

I'd also like to be able to use GlobalCSS outside of node_modules. This would help us incrementally adopt CSS Modules

@x5engine
Copy link

yeah this is very important! many npm packages are not working with nextjs but work with CRA, or other frameworks

@smurrayatwork
Copy link

For anyone else that's trying to use this with dart sass' js implementation for things like @use support and sass modules, if you have -any- other node module that has a dependency on node-sass, the default next setup will use node-sass instead of sass. Locally I've fixed it by doing the following:

// example next.config.js
module.exports = {
webpack(config, options) {
  config.module.rules.forEach(rule => {
          if (rule.oneOf) {
            const nestedScss = rule.oneOf.find((one) => {
              return one.test
                && 'some.scss'.match(one.test) 
                && one.issuer 
                && one.issuer.include 
                && one.issuer.include.includes('_app');
            });
            if (nestedScss) {
              const sassLoader = nestedScss.use.find(u => u.loader.includes('sass-loader'));
              // Set implementation to sass instead of node-sass here.
              sassLoader.options.implementation = require('sass');
            }
          }
        })
  }
}

You'll then need to import your scss files in _app.js.

@x5engine

This comment has been minimized.

@salazarm
Copy link

Also the restriction on it being exclusively _app.js is a little cumbersome.

If we're not going to support CSS references everywhere then could we make it so CSS can also be referenced by direct dependencies of _app (that are not referenced anywhere else)?
ie. It's fine if it's required by _app (and nowhere else) which would give the CSS a deterministic order based off imports.

It's not ideal however the use case I have is that I have is one codebase shared by multiple applications that import a shared module which imports shared CSS. I'd hate to duplicate those shared CSS imports in _app.js for every application. Currently to get around that I'd have to do some fancy js metaprogramming because we can't require css in other modules.

Instead I would like my current approach to work which is I have an "App Factory" which imports all the shared CSS. _app then uses the factory and imports its own CSS on top of the shared ones.

@theogravity
Copy link

I'm adding #13991 as I think it relates to this issue.

@Timer Timer modified the milestones: 9.x.x, iteration 3 Jun 15, 2020
@Timer Timer self-assigned this Jun 16, 2020
@harrisosserman
Copy link

+100 to this. I'm having to copy and paste node module css files into my project and adding a .module.css on them

@BernardA
Copy link

BernardA commented Jul 14, 2020

Here's another example.

In the case of the package pdf-viewer-reactjs its dependencies require CSS that need to be imported from _app.js as well.

This is bloating the CSS for the whole app and I am not sure about conflicts at this stage.

import 'react-quill/dist/quill.snow.css';
import 'react-image-crop/dist/ReactCrop.css';
import '../../node_modules/material-design-icons/iconfont/material-icons.css';
import '../../node_modules/bulma/css/bulma.css';
import '../../node_modules/bulma-helpers/css/bulma-helpers.min.css';

Additionally the following is output to console:

warn - ./node_modules/material-design-icons/iconfont/material-icons.css
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm
Location: node_modules/pdf-viewer-reactjs/dist/pdf-viewer-reactjs.js

./node_modules/bulma/css/bulma.css
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm
Location: node_modules/pdf-viewer-reactjs/dist/pdf-viewer-reactjs.js

./node_modules/bulma-helpers/css/bulma-helpers.min.css
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm
Location: node_modules/pdf-viewer-reactjs/dist/pdf-viewer-reactjs.js

./node_modules/material-design-icons/iconfont/material-icons.css
Module build failed: Error: Final loader (./node_modules/next/dist/build/webpack/loaders/error-loader.js) didn't return a Buffer or String

./node_modules/bulma/css/bulma.css
Module build failed: Error: Final loader (./node_modules/next/dist/build/webpack/loaders/error-loader.js) didn't return a Buffer or String

./node_modules/bulma-helpers/css/bulma-helpers.min.css
Module build failed: Error: Final loader (./node_modules/next/dist/build/webpack/loaders/error-loader.js) didn't return a Buffer or String

@michelt
Copy link

michelt commented Jul 27, 2020

Hi ! Do someone resolved this and how ? So many node modules that I can't import because of that.

@websocket98765
Copy link

Maybe using global styles in components could be activated via next.config.js, or an ugly console warning against global styles could be shown, in case there is concern about breaking from NextJS' best practices / opinions.

But this is important for users converting from CRA > NextJS. It's a blocker for us b/c we can't switch & then incrementally adopt things like CSS modules.

@soosap
Copy link

soosap commented Sep 29, 2020

Using next@9.5.4-canary.10 it is possible to import css anywhere in my application. But will the same be possible for scss files? I would like to import only the scss files that I am actually using on a page.

// pages/_app.tsx
import '../styles/common.scss'

// pages/index.tsx I use a Button
import '@mynpm/custom-ui/_Button.scss'

// pages/about.tsx I use a Carousel
import '@mynpm/custom-ui/_Carousel.scss'

@giupas
Copy link

giupas commented Oct 13, 2020

The example at https://nextjs.org/docs/basic-features/built-in-css-support
Schermata 2020-10-13 alle 16 43 19

Returns the error:
error - /Users/gp/dev/next-kolumbus/node_modules/@reach/dialog/styles.css
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm

@timneutkens
Copy link
Member

The example at https://nextjs.org/docs/basic-features/built-in-css-support
Schermata 2020-10-13 alle 16 43 19

Returns the error:
error - /Users/gp/dev/next-kolumbus/node_modules/@reach/dialog/styles.css
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm

Make sure you're on the latest version of Next.js.

@giupas
Copy link

giupas commented Oct 14, 2020

Sorry, I didn't specified it in the previous comment. I used version 9.5.5. just updated from npm.

@giupas
Copy link

giupas commented Oct 14, 2020

I cleared all the .next cache and now it works as expected.

@joeleduardo
Copy link

The error still there version 9.5.5, in _app --> import "react-gauge-chart-nextjs-support/dist/GaugeChart/style.css";

Screenshot 2020-11-12 at 14 12 11

bdavidxyz added a commit to mission-apprentissage/labonnealternance-old that referenced this issue Nov 19, 2020
@samuelcastro
Copy link

samuelcastro commented Dec 4, 2020

I'm still having this issue even on next 10.0.3. Removed all cache but I'm still getting:

error - ./node_modules/react-datepicker/dist/react-datepicker.css
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm

@softmarshmallow
Copy link

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

@bchurlinov
Copy link

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

@mfakhrusy
Copy link

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
Copy link

DantSu commented Mar 11, 2021

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
Copy link

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
Copy link

sekoyo commented Jul 30, 2021

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
Copy link
Member

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

@madroneropaulo
Copy link

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
Copy link

zigcccc commented Nov 8, 2021

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
Copy link
Member

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.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 27, 2022
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.