Parcel: How can I get CSS Modules to work with SCSS and multiple "layers" of imports?

Created on 3 Aug 2018  ·  4Comments  ·  Source: parcel-bundler/parcel

❔ Question

How can I get CSS Modules to work with SCSS and multiple layers of imports?

🔦 Context

I would like to get CSS Modules working with SCSS, multiple layers of imports, and multiple classes per element.

I have a naïve CSS Modules setup working with .postcssrc like so:

.postcssrc

{
    "modules": true,
}

With this, I can import a stylesheet with import styleA from "./stylesheetA.{scss,css}";, and use it with <div className={styleA.somethingSomething}>. I can use SCSS at will and @import things in ./stylesheetA.{scss,css} from ./stylesheetB.{scss,css}. Unfortunately, I cannot have multiple classes in className; in the case of className={styleA.something1 styleA.something2 styleB.something3}, only styleB.something3 will survive.

I also have a more involved CSS Modules setup working with .postcssrc and .babelrc like so:

.postcssrc

{
    "modules": true,
        "plugins": {
            "postcss-modules": {
                "generateScopedName": "[path]__[name]__[local]__[hash:base64:5]",
            },
        },
}

.babelrc

{
    "plugins": [
        ["react-css-modules", {
            "generateScopedName": "[path]__[name]__[local]__[hash:base64:5]",
            "handleMissingStyleName": "throw",
            "filetypes": {
                ".scss": {
                    "syntax": "postcss-scss",
                },
                ".sass": {
                    "syntax": "postcss-sass",
                },
            },
    }],
}

With this, I can import a stylesheet with import styleA from "./stylesheetA.{scss,css}"; and use it with <div styleName="styleA.something1">. In addition, I can use multiple classes from multiple sources, e.g. <div styleName="styleA.something1 styleA.something2"> or <div styleName="styleA.something1 styleA.something2 styleB.something3">. Still better, I can mix and match className and styleName. Unfortunately, I cannot use any class found in a file referenced by an @import statement in an imported stylesheet, e.g. ./stylesheetA.{scss,css}. This, in spite of the fact that I can see the desired class in the output main.[hashthingy].css! I also tried setting handleMissingStyleName to either warn or ignore, in which cases the build was successful (including the desired class making it into the final bundle) but the element was stripped of the desired class. (WTF, seriously?)

(In addition to babel-plugin-react-css-modules, I also tried react-class-modules, which also did not work.)

Is what I want even possible? If so, why is it not default? If not, what should I use instead?

💻 Code Sample

Here's the traceback from the CSS module does not exist error, because I find it so bizarre:

/usr/local/srv/www/tests/parcel_blank/src/main.jsx: /usr/local/srv/www/tests/parcel_blank/src/main.jsx: CSS module does not exist: rotate
    at getClassNameForNamespacedStyleName (/usr/local/srv/www/tests/parcel_blank/node_modules/babel-plugin-react-css-modules/dist/getClassName.js:47:13)
    at styleNameValue.split.filter.map.styleName (/usr/local/srv/www/tests/parcel_blank/node_modules/babel-plugin-react-css-modules/dist/getClassName.js:68:14)
    at Array.map (<anonymous>)
    at exports.default (/usr/local/srv/www/tests/parcel_blank/node_modules/babel-plugin-react-css-modules/dist/getClassName.js:66:6)
    at exports.default (/usr/local/srv/www/tests/parcel_blank/node_modules/babel-plugin-react-css-modules/dist/resolveStringLiteral.js:23:56)
    at PluginPass.JSXElement (/usr/local/srv/www/tests/parcel_blank/node_modules/babel-plugin-react-css-modules/dist/index.js:193:48)
    at newFn (/usr/local/srv/www/tests/parcel_blank/node_modules/babel-traverse/lib/visitors.js:276:21)
    at NodePath._call (/usr/local/srv/www/tests/parcel_blank/node_modules/babel-traverse/lib/path/context.js:76:18)
    at NodePath.call (/usr/local/srv/www/tests/parcel_blank/node_modules/babel-traverse/lib/path/context.js:48:17)
    at NodePath.visit (/usr/local/srv/www/tests/parcel_blank/node_modules/babel-traverse/lib/path/context.js:105:12)

🌍 Your Environment

| Software | Version(s) |
| ---------------- | ---------- |
| Parcel | 1.9.4
| Node | 8.11.3
| npm/Yarn | 6.2.0
| Operating System | MacOS 10.13.6 (High Sierra)

devDependencies from package.json:

"devDependencies": {
    "autoprefixer": "^9.0.1",
    "babel-plugin-react-css-modules": "^3.4.2",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "babel-preset-react": "^6.24.1",
    "node-sass": "^4.9.0",
    "parcel": "^1.9.4",
    "postcss-easy-import": "^3.0.0",
    "postcss-import": "^11.1.0",
    "postcss-modules": "^1.3.0",
    "postcss-nested": "^3.0.0",
    "postcss-nesting": "^6.0.0",
    "postcss-sass": "^0.3.2",
    "postcss-scss": "^2.0.0",
    "react": "16.4.1",
    "react-css-modules": "^4.7.4",
    "react-dom": "16.4.1",
    "react-router-dom": "^4.3.1"
}

Some of these were just stabs in the dark.

Question

Most helpful comment

Good news! This can be accomplished largely without .babelrc / .postcssrc fuckery. (.postcssrc still requires "modules": true.)

First method:

<div className={[styleA.something1, styleA.something2, styleB.something3].join(" ")]}>

Second method:

<div className={`${styleA.something1} ${styleA.something2} ${styleB.something3}`}>

Bad news: both are ugly and I find the latter quite difficult to easily determine whether the curly braces and backticks are all aligned properly

In sum: it works. With any luck, this post may save someone some trouble. For the future, maybe this should be in the Parcel documentation and/or a feature be added to Parcel or babel-plugin-react-css-modules (I'm still not entirely sure which is responsible) to make something like <div styleName="styleA.something1 styleA.something2 styleB.something3" className="global-img-style"> possible.

All 4 comments

Good news! This can be accomplished largely without .babelrc / .postcssrc fuckery. (.postcssrc still requires "modules": true.)

First method:

<div className={[styleA.something1, styleA.something2, styleB.something3].join(" ")]}>

Second method:

<div className={`${styleA.something1} ${styleA.something2} ${styleB.something3}`}>

Bad news: both are ugly and I find the latter quite difficult to easily determine whether the curly braces and backticks are all aligned properly

In sum: it works. With any luck, this post may save someone some trouble. For the future, maybe this should be in the Parcel documentation and/or a feature be added to Parcel or babel-plugin-react-css-modules (I'm still not entirely sure which is responsible) to make something like <div styleName="styleA.something1 styleA.something2 styleB.something3" className="global-img-style"> possible.

One more question: with modules enabled in .postcssrc, how do I get a global import? An entry in the entry point HTML file doesn't work, nor does a nameless entry (e.g. import "./stylesheetA.scss";) in any JS/JSX file. In the case of the latter, with modules enabled, the className of intended styled elements isn't transformed into Parcel's hashed-class version.

In the output JS, module.exports looks fine, I think:

module.exports = {
        "root": "_root_1amz2_1",
        "maximum-center": "_maximum-center_1amz2_9",
        "redtext": "_redtext_1amz2_16",
        "bigtext": "_bigtext_1amz2_19"
};

But the Javascript's createElement snippet is clearly wrong:

_reactDom2.default.render(_react2.default.createElement(
    "div",
    null,
    _react2.default.createElement(
        "p",
        { className: "redtext" },
        "something something something"
    )
), root);

Do people just not use CSS Modules around here? Honestly.

@BenSapiens className only accepts strings, so you need to pass a string in there. It's a common solution to use classnames, e.g.
className={classnames(a,b,c)}
Parcel has nothing to do with it actually.
HTH

@SpadarShut Thanks for the tip. Classnames seems to work very well indeed. I'm pretty familiar with HTML, CSS, and JS, but I guess I just don't have a firm grasp on what exactly Parcel does and doesn't do. As a software project it seems to be pretty solid — even endorsed by the author of Node — but the paucity of documentation, especially for those of us unfamiliar with the inner workings of "web compilers", is pretty striking.

Thanks again.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mnn picture mnn  ·  3Comments

philipodev picture philipodev  ·  3Comments

will-stone picture will-stone  ·  3Comments

466023746 picture 466023746  ·  3Comments

davidnagli picture davidnagli  ·  3Comments