vercel / next.js

The React Framework
https://nextjs.org
MIT License
125.09k stars 26.72k 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.

mikestopcontinues commented 4 years ago

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 commented 4 years ago

why did even switch to nextjs again?

salazarm commented 4 years ago

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

x5engine commented 4 years ago

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

smurrayatwork commented 4 years ago

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 commented 4 years ago

@smurrayatwork this is hacking not coding sorry

salazarm commented 4 years ago

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 commented 4 years ago

I'm adding https://github.com/vercel/next.js/discussions/13991 as I think it relates to this issue.

harrisosserman commented 4 years ago

+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 commented 4 years ago

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 commented 4 years ago

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

websocket98765 commented 4 years ago

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.

abdelrahmantoptal commented 4 years ago

Still unable to get around this. For my own needs I used a custom CSS handler, but this disables built-in CSS support but it may not be a good solution for all cases . The below is discouraged , use only until the package authors sort it out

next.config.js

const withCSS = require('@zeit/next-css'); const withPlugins = require('next-compose-plugins'); ... module.exports = withPlugins([ ... withCSS, ]);

websocket98765 commented 4 years ago

@abdelrahmantoptal's Do you know how to get that working for SASS?

It appears it would work for CSS, but throws an error when encountering a SASS import:

error - ./src/components/layouts/Footer.scss 1:0
Module parse failed: Unexpected character '@' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> @import 'styles/vars';
| 
| footer {

so I tried adding a SASS loader to the webpack config before using the withCSS plugin:

      config.module.rules.push({
        test: /\.s[ac]ss$/i,
        use: [
          // Creates `style` nodes from JS strings
          'style-loader',
          // Translates CSS into CommonJS
          'css-loader',
          // Compiles Sass to CSS
          'sass-loader'
        ]
      });

But that caused:

error - ./src/components/App.scss
ReferenceError: self is not defined

I also tried substituting @zeit/next-sass, but that caused the same error:

error - ./src/components/App.scss
ReferenceError: self is not defined

Any suggestions on how to tweak your code to use SASS?

johnnypea commented 4 years ago

Somebody by any chance successfully implemented https://github.com/balena-io-modules/rendition ?

https://github.com/balena-io-modules/rendition/issues/1164

OssiPesonen commented 4 years ago

I've now thrown out Gatsby and soon Next.js because of their small, but very blocking, opinionated features such as this one. I am now unable to use the CodeBlock plugin for CKEditor 5 because I can't get around this error. There should always be some way to circle these configurations.

michelt commented 4 years ago

It would be very helpful to have news from @Timer or someone from Vercel about this problem. This is a big problem with Next.js. Something is planned to fix this?

harrisosserman commented 4 years ago

Agreed! It's incredibly common to have css in node modules. As a developer, I have no control over how other developers structure their node modules, and other developers don't expect that putting css in a node module would break a web framework.

awlevin commented 4 years ago

@OssiPesonen have you seen this yet? This workaround isn’t ideal, but it solved the problem for me in the meantime.

OssiPesonen commented 4 years ago

@OssiPesonen have you seen this yet? This workaround isn’t ideal, but it solved the problem for me in the meantime.

I don't see how this helps? The problem is not me having to manually import some CSS files from node modules. The problem is npm packages doing the CSS importing in themselves. A package that includes a row like this:

import '../theme/stylesheet.css'

Will cause next.js to crash with a vengeance. And apparently the maintainer's advice is:

Reach out to the maintainer and ask for them to publish a compiled version of their dependency.

In what kind of fantasyland do people live in where they imagine you can just contact maintainers ask them to re-compile their package for you in a very fast pace? This will hinder anyone for weeks! This ticket has been open for 4 months. That's unacceptable when working on quickly moving projects.

Timer commented 4 years ago

We're going to be allowing importing CSS from node_modules into any component file within the next week (on canary)! We'll post here when it's ready to test.

BrandonE commented 4 years ago

If anyone needs this prior to the release, I was able to use the next-transpile-modules plugin to transpile the module from node_modules that was importing CSS. Worked like a charm for me.

rjoaopereira commented 4 years ago

@BrandonE seems that next-transpile-modules still needs to have the modules named *.module.css. Did you find a way around that?

BrandonE commented 4 years ago

@rjoaopereira I can't say I have a deep understanding of how any of this works, but most of my node_modules that imported CSS only worked with the @zeit/next-css plugin. Only one did not, at which point transpiling fixed the issue. Far from an elegant solution, and I hope that future versions of Next.js allow us to spend less time on Babel / Webpack alchemy and more on making web applications.

rjoaopereira commented 4 years ago

I got this almost working with the following changes.

next 9.5.3 next-transpile-modules 4.1.0 1st party components with emotion. 3rd party components with a mix of css modules and global css

scopedcomponents is to be replaced with the 3rd party components being used

//next.config.js
const withCustomWebpack = require("./webpack-custom.config");
const withNextCSSOverride = require("./next.config.css");
const withTM = require("next-transpile-modules")(["@scopedcomponents"]);

module.exports = withCustomWebpack(
  withTM(
    withNextCSSOverride({
      poweredByHeader: false
    })
  )
);
///next.config.css.js
const {
  getCssModuleLocalIdent
} = require("next/dist/build/webpack/config/blocks/css/loaders/getCssModuleLocalIdent");
const path = require("path");
/**
 * Stolen from https://stackoverflow.com/questions/10776600/testing-for-equality-of-regular-expressions
 */
const regexEqual = (x, y) => {
  return (
    x instanceof RegExp &&
    y instanceof RegExp &&
    x.source === y.source &&
    x.global === y.global &&
    x.ignoreCase === y.ignoreCase &&
    x.multiline === y.multiline
  );
};

module.exports = (nextConfig = {}) => {
  return Object.assign({}, nextConfig, {
    webpack(config, options) {
      const nextCssLoaders = config.module.rules.find(
        rule => typeof rule.oneOf === "object"
      );

      if (nextCssLoaders) {
        const nextCssLoader = nextCssLoaders.oneOf.find(
          rule =>
            rule.sideEffects === false &&
            regexEqual(rule.test, /\.module\.css$/)
        );

        if (nextCssLoader) {
          /***********************************************************
           * change the rule to match all scopedcomponents css files
           ***********************************************************/
          nextCssLoader.test = /(@scopedcomponents|react\-virtualized)\/.*\.css$/;

          const cssLoader = nextCssLoader.use.find(({ loader }) =>
            loader.includes("css-loader")
          );

          if (cssLoader) {
            /***********************************************************
             * Override the default behaviour for CSS modules discovery
             * auto = true makes webpack search for *.module.css
             * https://webpack.js.org/loaders/css-loader/#auto
             ***********************************************************/
            cssLoader.options.modules.auto = /@scopedcomponents\/.*\.css$/;
            /***********************************************************
             * Nextjs overrides the default mode to "Pure"
             * https://github.com/vercel/next.js/blob/v9.5.2/packages/next/build/webpack/config/blocks/css/loaders/modules.ts#L35
             * Put it back to normal
             ***********************************************************/
            cssLoader.options.modules.mode = "local";
            /***********************************************************************************************************************
             * There is a problem when using components built with css-modules with Nextjs.                                        *
             * NextJS will consume code from `lib` on the server side and from `es` on the client.                                 *
             * https://github.com/vercel/next.js/blob/v9.5.2/packages/next/build/webpack-config.ts#L374                            *
             * This raises a problem when generating the classes for different environments,                                       *
             * throwing an error of className mismatch due to the hash created being based on the file path                        *
             * https://github.com/vercel/next.js/blob/v9.5.2/packages/next/build/webpack/config/blocks/css/loaders/modules.ts#L26  *
             * https://github.com/webpack/loader-utils/blob/v1.4.0/lib/interpolateName.js#L39                                      *
             * To solve this, when generating the classNames for 3rd party components,                                                 *
             * we need to tell cssloader to always use the same path                                                               *                                                                          *
             *                                                                                                                     *
             *  https://github.com/zeit/next-plugins/issues/595                                                                    *
             ***********************************************************************************************************************/
            cssLoader.options.modules.getLocalIdent = (
              context,
              localIdentName,
              localName,
              options
            ) => {
              const newContext = { ...context };
              if (newContext.resourcePath.includes("@scopedcomponents")) {
                newContext.resourcePath = newContext.resourcePath.replace(
                  `${path.sep}es${path.sep}`,
                  `${path.sep}lib${path.sep}`
                );
              }
              return getCssModuleLocalIdent(
                newContext,
                localIdentName,
                localName,
                options
              );
            };
          }
        }
      }

      if (typeof nextConfig.webpack === "function") {
        return nextConfig.webpack(config, options);
      }

      return config;
    }
  });
};

Problems:

piotryordanov commented 4 years ago

@Timer any update on this?

dpatel4-lat commented 4 years ago

We're going to be allowing importing CSS from node_modules into any component file within the next week (on canary)! We'll post here when it's ready to test.

Will there be dynamic import of CSS from a component after this fix?

michelt commented 4 years ago

Thank you so much @Timer

Timer commented 4 years ago

next@^9.5.4-canary.10 now allows you to import Global CSS from node_modules anywhere in your application. This improves interoperability with third-party React libraries that require you import their CSS, but don't want it to increase bundle size for your entire application.

jackguoAtJogg commented 4 years ago

@Timer Can't wait to have that release, really appreciated your work 💯 ❤️

johmike commented 4 years ago

Thanks @Timer !

This has been a blocking issue for me currently, however when I tested this out today, still seeing the same error message. Is there anything more to it than simply upgrading to 9.5.4-canary-10? This example is trying to use 3rd party lib @rmwc

image

sasivarnan commented 4 years ago

@johmike Are you importing using the following syntax??

import "@rmwc/avatar/avatar.css";

Did you tried restarting the dev server after installing the latest version of next?

sasivarnan commented 4 years ago

@Timer Thanks a lot for this feature. Works great for importing CSS file from node_modules folder.

import 'prism-themes/themes/prism-darcula.css';

Any plans to support import of global css outside the node_modules dir?

johmike commented 4 years ago

@sasivarnan This is coming from another library that is importing the @rmwc components. That library is using @require("@rmwc/avatar/avatar.css"). I am importing import {Avatar} from "library/Avatar" and that is failing.

sasivarnan commented 4 years ago

@sasivarnan

This is coming from another library that is importing the @rmwc components. That library is using @require("@rmwc/avatar/avatar.css"). I am importing import {Avatar} from "library/Avatar" and that is failing.

Got it. I thought it was imported directly in your application. My bad.

OssiPesonen commented 4 years ago

Judging from the comments here, this actually isn't resolved or it was resolved but many people here report a different issue. Many people still cannot import modules, which import CSS from the packge itself (an import style.css statement inside a package file).

The fix seems to allow the app to import CSS from node_modules/ path, but there's a pretty easy way to go around this: just copy the CSS to your app for now until it's fixed. It's not a blocker level issue. So it did not really solve the blocker issue to which there is no easy solution. If you import a component which has an import statement to a CSS file the package itself contains, the app crashes.

Timer commented 4 years ago

@sasivarnan @OssiPesonen you both seem to be talking about a different issue than what was being discussed and fixed in this OP issue.

This specifically fixes libraries that require you import their CSS in your application, for example:

// components/MySlider.tsx
import { Slider } from "@reach/slider";
import "@reach/slider/styles.css";

function Example() {
  return <Slider min={0} max={200} step={10} />;
}

What you're talking about is a duplicate of #706 and #13282, or the ability to treat node_modules like first-party code.

johmike commented 4 years ago

@Timer I just tested the expected use case and it does indeed work fine.

When I import the css in a component directly as part of the next structure it works as expected, no errors. However if I move that component into another package outside of the next structure, build, and then install that package, it fails back to the same error as before.

It's possible something else is going on, as I'm not even using the Avatar component in this example, I am importing Button and yet Avatar is the failing error.

image

johmike commented 4 years ago

Also I added next-transpile-modules as we are working from a monorepo, but that didn't seem to help this particular issue.

johmike commented 4 years ago

I got this working by a strange configuration file from digging through a bunch of other issues around next-transpile-modules.

const withCSS = require("@zeit/next-css");
module.exports = withCSS();
require.extensions[".css"] = () => {
  return;
};

I eliminated next-transpile-modules and this works. I have no idea why, it seems like it shouldn't do anything?

johmike commented 4 years ago

I spoke too soon! While that does work for next dev, next build fails with an unknown token . (dot) error from one of the CSS files.

johmike commented 4 years ago

@Timer Any thoughts? Should this work out of the box with a monorepo and multiple packages? Or is there something else I need to configure so that @team/packageA can import css from node_modules and then be imported into @team/packageB?

Timer commented 4 years ago

You can follow https://github.com/vercel/next.js/issues/13282 for that behavior.

soosap commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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

giupas commented 3 years ago

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

joeleduardo commented 3 years ago

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
samuelcastro commented 3 years ago

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