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

How to use css-modules with other global css (discussion please don't merge it) #65

Open
wants to merge 1 commit into
base: master
from

Conversation

Projects
None yet
@lapanoid

lapanoid commented Oct 5, 2015

@markdalgleish @joshwnj
Initially I was trying to make two loaders working together

{
      test: /\.less$/,
      loader: 'style!css?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!less'
})
{
      test: /\.less$/,
      loader: 'style!css!less'
})

and made an issue for this #59
Bottom line of this was:
name all local css - *.module.(less|sass|whatever)
keep all global css - *.(less|sass|whatever)
and have these loaders

{
      test: /\.module.less$/,
      loader: 'style-loader!css-loader?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!less-loader'
}
{
      test: /^((?!\.module).)*less$/,
      loader: 'style!css!less'
}

This is ugly, but solves the problem on some level.
As @markdalgleish said there a lot caveats of having both global and local css, so I hope this PR could be a good place to discuss these caveats. The goal of this PR make reasonable FAQ on this matter.

@lapanoid

This comment has been minimized.

lapanoid commented Oct 5, 2015

I will stress test /^((?!\.module).)*less$/approach tonight, now my .module.less files does not include any .less files in themselves, if they will I think there could be a conflicts with loaders.

@lapanoid

This comment has been minimized.

lapanoid commented Oct 5, 2015

@import "./global.less";

from local.module.less

works just fine - no loaders conflicts

@joshwnj

This comment has been minimized.

Member

joshwnj commented Oct 5, 2015

Thanks @lapanoid good to hear you got something working. So when you use the code like in your example:

@import "./global.less";

from local.module.less, does that apply global.less as global styles, or does it add them as exports of the local.module.less module?

@lapanoid

This comment has been minimized.

lapanoid commented Oct 5, 2015

It add them as exports of the local.module.less module

@andresgutgon

This comment has been minimized.

andresgutgon commented Oct 24, 2015

It add them as exports of the local.module.less module

So what happen if I import global.less in second local module? Is that CSS duplicated?

@lapanoid

This comment has been minimized.

lapanoid commented Nov 16, 2015

@andresgutgon I guess no - webpack treats this as any other module, when you require same js module it will just reuse it - not duplicate it in bundle, same is here

@lapanoid

This comment has been minimized.

lapanoid commented Nov 16, 2015

I guess we can remove .module. stuff and make this approach #59 (comment) possible with this https://github.com/jsdf/webpack-combine-loaders

@themitchy

This comment has been minimized.

themitchy commented Dec 21, 2015

Discussion seems stalled out... is there a recommended way to do this for the time being? Right now we're looking at gulp scripts doing a concat with a hardcoded list of "node_modules/..." paths, which is pretty janky to say the least :-P

@sohkai

This comment has been minimized.

sohkai commented Feb 22, 2016

@lapanoid I came across this problem recently, trying to declare bootstrap dependencies from my CSS files that were later being processed into CSS Modules. For now I've settled with loading bootstrap externally, using another loader to accomplish this, but I realized we could write a PostCSS plugin that could parse a syntax that would just put a :global() block around the import.

For example, something like:

@global-import from 'bootstrap/buttons';

would turn into

:global {
...
styles from 'bootstrap/buttons';
...
}

This doesn't solve the problem with requiring global CSS in JS, but I would argue that since it's a CSS dependency, it'd be better as an import in the CSS.

@lapanoid

This comment has been minimized.

lapanoid commented Feb 24, 2016

@sohkai thanks for feedback and I actually like the idea of post-css plugin, seems like right solution.
Stupid question:
If we just wrap all css in file with :global it will do the case?
Of we need to wrap every class f.e.?

@sohkai

This comment has been minimized.

sohkai commented Feb 24, 2016

@lapanoid Hmm, good point; I had the impression that :global would work on nested classes just like that, but from what #115, #39, and #96 have, it looks like the examples are just getting expanded with postcss-nested and applied before each nested class. That makes sense too, since the css module loaders probably don't understand nested CSS yet.

In that case, the plugin would require some more work, but I think it's quite feasible to look for classnames and add :global() to all of them after preprocessors and postcss-nested has run.

@themitchy

This comment has been minimized.

themitchy commented Feb 25, 2016

We've solved this with standard @import syntax and postcss-import used in postcssAfter. Everything pulled in via @import will be pulled in un-modified as global css.

@import 'node_modules/stuff-that-should-remain-global';

// regular css-modules stuff below here
@sohkai

This comment has been minimized.

sohkai commented Feb 25, 2016

@lapanoid Actually, looking at the local-by-default plugin, it looks like :global would work if we just wrapped it, but this would require postcss-nested to be run after:

// your import; foo contains .foo .bar { }
@global-import 'foo';

// we wrap :global around it
:global {
.foo .bar { }
}

// then postcss-nested runs
:global .foo .bar { }

// then local by default runs
.foo .bar { }

I don't like this due to the order dependencies (ie. postcss-nested having to run after; this is redundant if someone uses a preprocessor), so another solution would be to just add :global before every root-level class declaration:

/**
 * Your import, assuming you're running postcss-nested after
 * rather than a preprocessor or postcss-nested before.
 * foo contains .foo .bar { }, .foo { .baz { } &-bar { } }
 */
@global-import 'foo';

// we add :global to all the class declarations
:global .foo .bar { }
:global .foo { .baz { } &-bar { } }

// postcss-nested runs
:global .foo .bar { }
:global .foo .baz { }
:global .foo-bar { }

// then local by default runs
.foo .bar { }
.foo .baz { }
.foo-bar { }

This has the benefit of not needing to care about nested rules so users could choose if they wanted a preprocessor to do that or postcss-nested.

@themitchy That's pretty neat! It's surprising how easy it is to set up this with browserify if you're just using postcss. I'd still argue for a new import syntax rather than the native @import to support preprocessors and other build systems (ie. webpack, where the order of the loaders prevents postcss-import from achieving this).

@pgherveou

This comment has been minimized.

pgherveou commented Feb 29, 2016

@themitchy, I like your solution, but what do you mean by

We've solved this with standard @import syntax and postcss-import used in postcssAfter

how do you configure this postcssAfter? I havent found how to do that with webpack and postcss

@sohkai

This comment has been minimized.

sohkai commented Mar 8, 2016

@pgherveou I'm not sure this can be done in webpack due to the order of the loaders; the postcssAfter @themitchy was talking about is from css-modulesify plugin for browserify.

@pgherveou

This comment has been minimized.

pgherveou commented Mar 8, 2016

Ok thks for your help

Sent from my iPhone

On Mar 8, 2016, at 8:24 AM, Brett Sun notifications@github.com wrote:

@pgherveou I'm not sure this can be done in webpack due to the order of the loaders; the postcssAfter @themitchy was talking about is from css-modulesify plugin for browserify.


Reply to this email directly or view it on GitHub.

@geelen

This comment has been minimized.

Member

geelen commented Mar 9, 2016

Yeah, my way of thinking is, a CSS Module should only import information relative to it.

Basically, the CSS you write exists in a murky world. You're going to have user agent styles, plus a reset or a normalise, plus whatever global stuff you want to add, but the module file itself should be pure. It should feel like writing a new, better CSS. If you want to add global css, add it outside of a module file.

I'm not sure how easy webpack or browserify makes this. Maybe only invoke the css modules loader chain on files that don't match .global.css or something? But yeah, trying to shoehorn a file full of globals just so it passes nicely though the css modules postcss chain doesn't feel like the cleanest way.

@sohkai

This comment has been minimized.

sohkai commented Mar 10, 2016

@geelen I agree that the module file should be pure, but realistically, I think most people will still have to deal with global stylesheets that weren't written with CSS Modules in mind. compose: ... from global handles cases where I want to extend a global style nicely, but it'd also be nice to have a way to declare (and load) those global dependencies from within a CSS module without resorting to wrapping them in your own .global.css file.

I guess this is painfully obvious in hindsight, but importing into global works if you just nest the import in a :global, ie. :global { @import 'foo' }. See https://github.com/davezuko/react-redux-starter-kit/blob/master/src/styles/core.scss; this is simple enough that there doesn't need to be a plugin. I think this also handles multiple modules importing the same file (ie. it's only imported once in the final file), assuming that all the modules have the import wrapped in a :global, but I haven't checked yet. Edit: Unfortunately, with the way Sass does importing, having multiple modules importing the same file means that imported file will be duplicated multiple times.

@chrisblossom

This comment has been minimized.

chrisblossom commented Apr 9, 2016

@geelen is davezuko/react-redux-starter-kit#701 not the recommended/a bad solution? Should I be importing normalize.css outside of css-modules, possibly as a webpack entry point instead?

@sohkai

This comment has been minimized.

sohkai commented Apr 25, 2016

Unfortunately, there's a bit of a gotcha with :global { @import 'foo'; }. This pattern doesn't work if you use a preprocessor / postcss plugin that allows the & operator; when the imported stylesheet uses a & to reference a root class and adds a class before it, that class doesn't fall inside the :global declaration. Simplified, the pattern looks like:

// your file
:global { @import 'foo.scss'; }

// foo.scss
.foo {
    .bar & { }
}

// In your file, foo.scss gets generated to:
:global .foo { }
.bar :global .foo { }

// So .bar will be mistakenly localized

A good live example of this is in bootstrap-sass.

@whtouche

This comment has been minimized.

whtouche commented May 23, 2016

I think it's super important to mention explicitly (because I struggled with this for a couple of days and didn't find this mentioned anywhere) that in order to use the syntax
:global { @import '../../node_modules/fixed-data-table/dist/fixed-data-table.min.css'; }
for example, you must be using a pre-processor (such as sass in my case) that understands / handles nesting.

I have not found a way to import 3rd party css into the global scope using vanilla css + modules. If anyone can help with that it would be super appreciated, because it seems excessive to run my css through a pre-processor just for this one use case.

@aaronatmycujoo

This comment has been minimized.

aaronatmycujoo commented Sep 21, 2017

is there a way to compose only the class name? this would help with using the DllPlugin, or be composing from a library that you are extracting into some kind of vendor.css file and avoid duplicate css.

crazymousethief added a commit to NiX-Team/purple-gourd that referenced this pull request Nov 29, 2017

Add a new login site (use antd)
CSS modules conflicts with antd, pr css-modules/css-modules#65
@AlexanderTserkovniy

This comment has been minimized.

AlexanderTserkovniy commented Dec 6, 2017

Guys I am developing some library for our project let's call it ui-components and we have main application let's call it frontend. We use bootstrap and I want to redefine some bootstrap classes with our fonts, which are defined in the library e.g.:

:global {
  & .form-group {
    label {
      /*here I want to use .font-medium; from 'ui-components/src/common-styles/font.pcss'*/
      color: var(--N700);
      margin: 4px 0;
      width: 100%;
    }
  }
}

So, I cannot compose it, because:

label selector looks weird it is not :local

But I need to inherit that, I do not want to write another class just to repeat myself, that's why we have components library.

How to achieve that?

@aaronatmycujoo

This comment has been minimized.

aaronatmycujoo commented Dec 11, 2017

you can't compose global styles because composing just concatenates the class name. you only compose when you also control the element's class name. for example:

with this:

.font-medium {
   font-weight: 600;
   font-size: 16px;
}

and then this:

.form-group-label {
    composes: font-medium from 'ui-components/src/common-styles/font.pcss';
    color: var(--N700);
    margin: 4px 0;
    width: 100%;
}

composing would do this by concatenating the class name:

import styles from './styles.css';
styles['form-group-label'] // is a string with: ".form-group-label.font-medium"

rather than altering the css and doing (what i think you want it to do) this:

.form-group-label {
   font-weight: 600;
   font-size: 16px;
   color: #000;
   margin: 4px 0;
   width: 100%;
}
@carlos121493

This comment has been minimized.

carlos121493 commented Dec 26, 2017

use '!style-loader!css-loader!xxx' works for me with webpack3+

https://webpack.js.org/guides/migrating/#automatic-loader-module-name-extension-removed

@0xcaff 0xcaff referenced this pull request Jan 2, 2018

Merged

Added Filter Parameter #327

@0xcaff

This comment has been minimized.

0xcaff commented Jan 2, 2018

This can be done easily with resource queries. Here's the webpack rule:

{
  rules: [
    {
      oneOf: [
        {
          test: /\.css$/,
          resourceQuery: /^\?raw$/,
          use: [require.resolve("style-loader"), require.resolve("css-loader")]
        },
        {
          test: /\.css$/,
          use: [
            require.resolve("style-loader"),
            {
              loader: require.resolve("css-loader"),
              options: {
                importLoaders: 1,
                modules: true,
                localIdentName: "[name]__[local]___[hash:base64:5]"
              }
            },
            require("./postcss-loader")
          ]
        }
      ]
    }
  ];
}

Here's how you would use it:

import './global.css?raw';
@joewestcott

This comment has been minimized.

joewestcott commented Jan 3, 2018

Thankyou for the above answer, @0xcaff!

However it's throwing errors when adapting this technique to my webpack config. I think this is because there's nothing in the second rule to prevent .css files that had been matched from the rule above being matched again in the rule below.

I've adapted your answer to use webpack's oneOf:[], so each variation can only be matched once.
I also like this approach because it keeps the css config together, and uses fewer lines as there's
only one test: /\.css$/,.

rules: [{
    test: /\.css$/,
    oneOf: [{
        resourceQuery: /^\?raw$/,
        use: [
            require.resolve('style-loader'),
            require.resolve('css-loader')
        ]
    }, {
        use: [
            require.resolve('style-loader'),
            {
                loader: require.resolve('css-loader'),
                options: {
                    importLoaders: 1,
                    modules: true,
                    localIdentName: '[name]__[local]___[hash:base64:5]'
                }
            },
            require('./postcss-loader')
        ]
    }]
}]

Again, we can use

import './global.css?raw'
@0xcaff

This comment has been minimized.

0xcaff commented Jan 3, 2018

Good catch @joewestcott. There was a oneOf which I forgot to add back. I'll edit my post.

@crissdev

This comment has been minimized.

crissdev commented Jan 8, 2018

I opted for postcss-modules as it has the globalModulePaths option. I also integrated babel-plugin-react-css-modules.

// Configuration taken from webpack.config.dev.js

{
  test: /\.css$/,
  use: [
    require.resolve('style-loader'),
    {
      loader: require.resolve('css-loader'),
      options: {
        importLoaders: 1,
        imports: false,
        modules: false,
        sourceMap: false
      }
    },
    {
      loader: require.resolve('postcss-loader'),
      options: {
        // Necessary for external CSS imports to work
        // https://github.com/facebookincubator/create-react-app/issues/2677
        ident: 'postcss',
        plugins: () => {
          const escapeRegex = require('escape-string-regexp');
          return [
            require('postcss-flexbugs-fixes'),
            autoprefixer({
              flexbox: 'no-2009',
            }),
            require('postcss-modules')({
              globalModulePaths: [escapeRegex(paths.appNodeModules)],
              generateScopedName: '[name]__[local]___[hash:base64:5]',
              getJSON: () => {},
            })
          ];
        }
      }
    }
  ]
};

The configuration was obtained from create-react-app after running eject.

@AlexanderTserkovniy

This comment has been minimized.

AlexanderTserkovniy commented Jan 12, 2018

@aaronatmycujoo that really sucks... I want just to extend the class, which has already been written in one place, just to DRY, but I need to recreate it locally and then compose :(

@aaronatmycujoo

This comment has been minimized.

aaronatmycujoo commented Jan 15, 2018

But how can it compose a class name when you are targeting a generic element? And especially when you are using :global:. A mixin is probably what you are looking for.

@pietrofxq

This comment has been minimized.

pietrofxq commented Jan 18, 2018

Is there already a best solution for importing external css? I'd like to add normalize.css and some other external css resources. Should I go for @0xcaff approach? Or is there a better solution? I was thinking that maybe using the .pcss extension for all the project files and running css modules only on them could be a nice workaround

@aaronatmycujoo

This comment has been minimized.

aaronatmycujoo commented Jan 18, 2018

https://github.com/postcss/postcss-import or

import '!style-loader!css-loader!normalise.css'

@pietrofxq

This comment has been minimized.

pietrofxq commented Jan 19, 2018

@aaronatmycujoo just using postcss import won't make the imported libraries not use css modules so that's not enough. Looks like the workaround in the first post of this thread seems to be the best way until now - as long as no library or external file add ".module" to their css files.

@pietrofxq

This comment has been minimized.

pietrofxq commented Jan 23, 2018

Ok I've tested the first suggestion, by the author of this thread. It works nice, but I found out it doesn't make sense to have .modules.css instead of global.css, because there's usually only one or few global css files. Having .module.css in all your components css file makes it harder to navigate between tabs in the editor and also it's more typing when importing. I changed the regex to this one, that allows a file called global.css or {name}.global.css:
Modules: /^((?!\.?global).)*css$/
Global: /\.?global.css$/

@jamesmcintyre

This comment has been minimized.

jamesmcintyre commented Feb 10, 2018

@pietrofxq this worked perfectly for me! For the few third-party styles I need that aren't css-modules I can easily import this way.

@fatlinesofcode

This comment has been minimized.

fatlinesofcode commented Jul 2, 2018

this can also be done in a single loader with custom getLocalIdent function. I've set mine up to look for .global.scss filenames. if they are matched the module naming is not applied e.g.

{
          test: /\.scss$/,
          loaders: ['style-loader',
            {
              loader: 'css-loader',
              options: {
                modules: true,
                getLocalIdent: (loaderContext, localIdentName, localName, options) => {
                  const fileName = path.basename(loaderContext.resourcePath)
                  if (fileName.indexOf('global.scss') !== -1) {
                    return localName
                  } else {
                    const name = fileName.replace(/\.[^/.]+$/, "")
                    return `${name}__${localName}`
                  }
                },
                // localIdentName: '[path][name]__[local]--[hash:base64:5]'
              }
            }, 'sass-loader'],
          exclude: /node_modules/,
        }
@nevace

This comment has been minimized.

nevace commented Aug 10, 2018

I wanted css modules to use the localIdentName pattern while stylesheets I specify use the usual class name. I ended up using the internal css-loader getLocalIdent function in my own function:

const getLocalIdent = require('css-loader/lib/getLocalIdent');

{
  loader: 'css-loader',
  options: {
    modules: true,
    localIdentName: '[path][name]__[local]--[hash:base64:5]',
    getLocalIdent: (loaderContext, localIdentName, localName, options) => {
      return loaderContext.resourcePath.includes('semantic-ui-css') ?
        localName :
        getLocalIdent(loaderContext, localIdentName, localName, options);
    }
  }
}
@manuelro

This comment has been minimized.

manuelro commented Oct 23, 2018

this can also be done in a single loader with custom getLocalIdent function. I've set mine up to look for .global.scss filenames. if they are matched the module naming is not applied e.g.

{
          test: /\.scss$/,
          loaders: ['style-loader',
            {
              loader: 'css-loader',
              options: {
                modules: true,
                getLocalIdent: (loaderContext, localIdentName, localName, options) => {
                  const fileName = path.basename(loaderContext.resourcePath)
                  if (fileName.indexOf('global.scss') !== -1) {
                    return localName
                  } else {
                    const name = fileName.replace(/\.[^/.]+$/, "")
                    return `${name}__${localName}`
                  }
                },
                // localIdentName: '[path][name]__[local]--[hash:base64:5]'
              }
            }, 'sass-loader'],
          exclude: /node_modules/,
        }

I'm afraid this may end up generating the modules and the standard CSS too, which will duplicate file size. Please correct me if I'm wrong.

@AJIh

This comment has been minimized.

AJIh commented Nov 4, 2018

I wanted css modules to use the localIdentName pattern while stylesheets I specify use the usual class name. I ended up using the internal css-loader getLocalIdent function in my own function:

const getLocalIdent = require('css-loader/lib/getLocalIdent');

{
  loader: 'css-loader',
  options: {
    modules: true,
    localIdentName: '[path][name]__[local]--[hash:base64:5]',
    getLocalIdent: (loaderContext, localIdentName, localName, options) => {
      return loaderContext.resourcePath.includes('semantic-ui-css') ?
        localName :
        getLocalIdent(loaderContext, localIdentName, localName, options);
    }
  }
}

@nevace Your solution is close to perfect. 👍 Though it still exports class names in global stylesheets (like semantic-ui-css) to the identifiers map of css modules, which are not necessary.

@ethansisson

This comment has been minimized.

ethansisson commented Nov 6, 2018

@nevace That is almost exactly what I was looking for. The only change I made is rather than exclude a specific library name, I test for any path that includes "node_modules". That may not work if you need to load global css that isn't from an npm package, of course. Works perfectly for my project.

@nevace

This comment has been minimized.

nevace commented Nov 6, 2018

@nevace That is almost exactly what I was looking for. The only change I made is rather than exclude a specific library name, I test for any path that includes "node_modules". That may not work if you need to load global css that isn't from an npm package, of course. Works perfectly for my project.

I ended up doing the same actually, I had a regex with paths in by the end of it. It would be good if an 'exclude' property could be added to the config which is an array of paths to exclude (globs would be better) and the function above being implemented in the css-modules library itself. I haven't been keeping track of this but what do the maintainers think? I'm happy to give it a go and submit a PR if it is likely to be merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment