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 Global CSS in Next.js with Webpack #16879

Closed
smhmd opened this issue Jun 9, 2021 · 12 comments
Closed

Importing Global CSS in Next.js with Webpack #16879

smhmd opened this issue Jun 9, 2021 · 12 comments

Comments

@smhmd
Copy link

smhmd commented Jun 9, 2021

Current behavior

Following this migration guide, it is recommended to add (global) stylesheets to your tests. Nextjs's webpack wouldn't allow you to add them to your components anyway. But, when you add styles to a test, all tests (in cypress open-ct) hang on Your tests are loading....

import 'tailwindcss/tailwind.css' // or '../styles/index.css'

import { mount } from '@cypress/react'
import Button from '.'

describe('<Button />', () => {
  it('should mount', () => {
    mount(<Button />)
    cy.contains('click me')
  })
})

Desired behavior

For the global styles to get applied.

Test code to reproduce

// cypress/plugins/index.ts
const injectNextDevServer = require('@cypress/react/plugins/next')

const pluginConfig: Cypress.PluginConfig = (on, config) => {
  if (config.testingType === 'component') {
    injectNextDevServer(on, config)
  }

  return config
}

module.exports = pluginConfig

Versions

Cypress package version: 7.5.0
Cypress binary version: 7.5.0
Electron version: 12.0.0-beta.14
Bundled Node version: 14.15.1
OS: (Linux) 5.10.36-2-MANJARO
@elevatebart
Copy link
Contributor

Hello @smhmd

I really want to help you but I will need a bit more info:

  • What verison of Next and webpack are you using
  • What is your tailwind format? Do you use just in time (JIT)?

The simplest way to have you bug fixed looked at is always the same: make a repro-repo.

I created this minimal repository with tailwindcss working for both nextjs and Cypress CT:
https://github.com/elevatebart/tailwind-next

You can fork it and try to reproduce the error you see?

Once you do, you can copy the link here in the comments and I will help you more.

@smhmd
Copy link
Author

smhmd commented Jun 14, 2021

@elevatebart Thank you very much. Placing the tailwindcss/tailwind.css import in a support file did the trick.
However, placing any global stylesheets there result in an error:

ERROR in ./styles/index.css
Module Error (from ./node_modules/next/dist/build/webpack/loaders/error-loader.js):
Global CSS cannot be imported from files other than your Custom <App>. Please move all global CSS imports to pages/_app.js. Or convert the import to Component-Level CSS (CSS Modules).
Read more: https://nextjs.org/docs/messages/css-global

I would imagine simply modifying webpack's config and replacing the part that's responsible for this check would fix the issue.
If you think this is something that needs to be addressed by @cypress/react/plugins/next, feel free to keep this issue open, otherwise, I think it can be closed.

Repo

Again, thanks a lot for the help, man.

Deps:

{
  "next": "10.2.3",
  "webpack": "4",
}

@natterstefan
Copy link

natterstefan commented Aug 5, 2021

Hi @smhmd and @elevatebart,

thanks for the demo repo and the info about the support/index.js trick. I tried it too, but it does not work. I can't import a css file from support/index.js nor from a folder in src for instance.

I always get this error:

Global CSS cannot be imported from files other than your Custom <App>. Please move all global CSS imports to pages/_app.js. Or convert the import to Component-Level CSS (CSS Modules).
Read more: https://nextjs.org/docs/messages/css-global
Location: cypress/support/index.ts

You can see a demo repo and where I am trying to add it here natterstefan/nextjs-template#48 - it's basically the same you are trying to do in the linked PR.

I have no clue how to solve that at the moment. 🤔


Update

I would imagine simply modifying webpack's config and replacing the part that's responsible for this check would fix the issue.

You mean a solution like this one vercel/next.js#12079 (comment) or this one vercel/next.js#12079 (comment)? That could solve it, I guess.

@smhmd
Copy link
Author

smhmd commented Aug 5, 2021

Hello @natterstefan.

At last, I resorted to a brand new Webpack configuration. The downside is that if you change Webpack in next.config.js, you need to change it here as well (and possibly in Storybook configuration, too.) You can try importing from one central configuration, but I didn't try that.

These are the dependencies I believe are needed:

I am using Webpack 4 because TailwindCSS JIT does not work correctly with Webpack 5 and Next.js, yet. Therefore, css-loader, postcss-loader, and style-loader need be these exact versions or less. tsconfig-paths-webpack-plugin is used to help resolve TypeScript aliases (e.g., import Comp from '~/components/Comp')

{
    "@babel/core": "7.14.8",
    "@cypress/react": "5.9.3",
    "@cypress/webpack-dev-server": "1.4.0",
    "babel-loader": "^8.2.2",
    "css-loader": "5.2.7",
    "postcss-loader": "4.3.0",
    "style-loader": "2.0.0",
    "tsconfig-paths-webpack-plugin": "3.0.0",
    "webpack": "4",
    "webpack-dev-server": "3"
}    
yarn add -D @babel/core @cypress/react @cypress/webpack-dev-server babel-loader css-loader postcss-loader style-loader tsconfig-paths-webpack-plugin webpack webpack-dev-server

Create a webpack.test.js at the root of your project. and add the following to your plugins/index.ts (you could ignore coverage lines).

// define the webpack file name
const webpackFilename = 'webpack.test.js'

const pluginConfig: Cypress.PluginConfig = (on, config) => {
  require('@cypress/code-coverage/task')(on, config)
  
  // use the webpack configration in unit tests
  if (config.testingType === 'component') {
    // require load-webpack from cypress react plugins 
    require('@cypress/react/plugins/load-webpack')(on, config, {
      webpackFilename,
    })
    on('file:preprocessor', require('@cypress/code-coverage/use-babelrc'))
  }

  return config
}

module.exports = pluginConfig

Inside webpack.test.js, I have the following (in addition to SVGR rule I didn't include):

/**
 * This webpack configuration is used in Cypress component testing
 * Because Next.js configuration is too restrictive.
 * For example, you can't import global CSS or export * from any file.
 */

const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')

/** @type { import('webpack').Configuration } */
const config = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.(ts|tsx|js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          { loader: 'css-loader', options: { url: false } },
          'postcss-loader',
        ],
      },
    ],
  },
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
    modules: ['node_modules'],
    plugins: [new TsconfigPathsPlugin()],
  },
}

module.exports = config

In your cypress.json, add the following:

{
  "$schema": "https://on.cypress.io/cypress.schema.json",
  "component": {
    "supportFile": "cypress/support/ct.ts"
  },
}

This makes unit tests have a separate support file. In that support file (support/ct.ts), include the following:

/**
 * This file defines cypress component testing specific support imports (as defined in `cypress.json#components.supportFile`)
 * We can import files that are not needed in e2e testing.
 */
import '.'
import 'focus-visible'
import 'tailwindcss/tailwind.css'
import '~/styles/prose.css'

Import everything you have in _app and whatever you need. We also do import '.' to execute everything from the original support file.

One thing that isn't working with this setup is next/image Image component. I was only using it once, so I just removed it alongside its Eslint rule. In Storybook, you simply mock the component's effect out. It would be nice to do something like this in Cypress.

The Webpack config is definitely incomplete (I've been using it for two days). I would love to get suggestions to have a solid base configuration for React components in Next.

@smhmd smhmd changed the title Nextjs component tests hang after adding stylesheets Importing Global CSS in Next.js with Webpack Aug 5, 2021
@leerob
Copy link

leerob commented Aug 7, 2021

@smhmd FYI, Tailwind JIT does work with webpack 5 in Next.js. I would recommending using 5 over 4 for the best performance and HMR speed.

@smhmd
Copy link
Author

smhmd commented Aug 7, 2021

You might be right, @leerob. But I also think that Tailwind JIT does not work with Webpack 5 in Cypress component testing.

import 'tailwindcss/tailwind.css in support/ct.ts does not work when the dependency is "webpack": "5". using "webpack": "4" magically fixes it. Maybe I should file an issue with a reproduction repo.

@natterstefan
Copy link

natterstefan commented Aug 7, 2021

But I also think that Tailwind JIT does not work with Webpack 5 in Cypress component testing.

It does work with Webpack 5, Tailwind's JIT, and Cypress component testing when you take care (removing and adding (s)css loaders again to Webpack) of this rule from Next's webpack config: https://github.com/vercel/next.js/blob/74b159c850f2a79ca6f5efa6a308fe9e95dc1c1e/packages/next/build/webpack/config/blocks/css/index.ts#L269-L287.

I set up a demo repo later to show how I did it (but it's only a proof of concept).


One thing that isn't working with this setup is next/image Image component. I was only using it once, so I just removed it alongside its Eslint rule. In Storybook, you simply mock the component's effect out. It would be nice to do something like this in Cypress.

The Webpack config is definitely incomplete (I've been using it for two days). I would love to get suggestions to have a solid base configuration for React components in Next.

🤔 Yeah, the image does not work in my POC setup too. The image's URL does not resolve an image.

Update: But I can take care of that by intercepting all image requests.

  cy.intercept('/_next/*', {
    fixture: 'image.jpeg',
  })

@JessicaSachs
Copy link
Contributor

We're working on designing the API for global CSS imports right now. It seems like Next.js can be pretty restrictive when it comes to supporting developer tools/non-Next.js environments. (Storybook has these problems, too).

If you folks (@natterstefan or @smhmd) have any ideas for how to solve this going forward, we'd like some input :-)

@natterstefan
Copy link

Hi @JessicaSachs,

thanks for the update. I understand why they added that rule to their Webpack config, but I wished they had added a toggle to turn it off as well.

Maybe @leerob can also help us here.

We're working on designing the API for global CSS imports right now.

Will this become part of Cypress or part of @cypress/react/plugins/next?

(Storybook has these problems, too)

I know, and I had problems with it too.

If you folks (@natterstefan or @smhmd) have any ideas for how to solve this going forward, we'd like some input :-)

Sure, I'll keep you posted if I come up with any valuable ideas.

@leerob
Copy link

leerob commented Aug 18, 2021

We are going to allow global CSS imports 👍

vercel/next.js#27953

@JessicaSachs
Copy link
Contributor

Awesome @leerob! It's still in RFC stage, and I don't know what that means for when it'll get released. If you have more context on the timing, lmk.

I'm going to close this issue with the guidance to "Update your webpack config" until Next.js supports adding global styles natively.

@penx
Copy link
Contributor

penx commented Dec 16, 2021

This is a bit hacky, mainly due to assuming that the CSS rules are at [2] in webpackConfig.module.rules and importing findNextWebpackConfig from a subroute, but may be a useful starting point for anyone looking for a solution without creating a custom webpack config

cypress/plugins/index.js

const findNextWebpackConfig = require('@cypress/react/plugins/next/findNextWebpackConfig');

async function devServer(cypressDevServerConfig) {
  const webpackConfig = await findNextWebpackConfig(
    cypressDevServerConfig.config
  );

  webpackConfig.module.rules[2].oneOf.unshift({
    test: [/(?<!\.module)\.css$/, /(?<!\.module)\.(scss|sass)$/],
    use: ['next-style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
  });
  const { startDevServer } = require('@cypress/webpack-dev-server');

  return startDevServer({
    options: cypressDevServerConfig,
    webpackConfig,
    template: require.resolve('@cypress/react/plugins/next/index-template.html')
  });
}

function nextDevServer(on, config, ...args) {
  on('dev-server:start', (cypressDevServerConfig) => {
    return devServer(cypressDevServerConfig, ...args);
  });
  config.env.reactDevtools = true;
  return config;
}

module.exports = (on, config) => {
  nextDevServer(on, config);
  return config;
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants