gajus / babel-plugin-react-css-modules

Transforms styleName to className using compile time CSS module resolution.
Other
2.05k stars 162 forks source link

SCSS loops doesn't produce styleNames as expected #270

Closed bohdanbirdie closed 4 years ago

bohdanbirdie commented 4 years ago

Having SCSS code

.m-social-share-btn {
  //.....
    $social-colorrr: (
        ("twitter", $color-twitter),
        ("linkedin", $color-linkedin),
    ) !default;

    @each $name, $color in $social-colorrr {
        &--#{$name} {
            background-color: $color;
        }
    }
}

And having React code

import '../SocialShareButtons.scss';

const LinkedinShareButton = ({
    className,
}) => (
    <button
        className={className}
        styleName="m-social-share-btn m-social-share-btn--linkedin"
        type="button"
      />)

Receiving message Could not resolve the styleName 'm-social-share-btn--linkedin'.

The same thing happens for every other case when I'm having @each rule used. Already tried to use postcss-advanced-variables but it didn't work because the plugin is async. Also tried many other PostCSS plugins, without results. (postcss-each, postcss-each-scss, etc).

So I wonder whether I'm dumb or it doesn't work?

I followed the example from @hinok re.: import plugin and wrote my own resolver. Everything else seems to work tho. My babel.config.js

const path = require('path');

const convertToScssImport = (filePath) => {
    const parsedFilePath = path.parse(filePath);
    if (parsedFilePath.ext) {
        return filePath;
    }
    parsedFilePath.base = `_${parsedFilePath.base}.scss`;
    return path.format(parsedFilePath);
};

// eslint-disable-next-line func-names
module.exports = function (api) {
    api.cache(true);

    const plugins = [
        [
            'postcss-import-sync2',
            {
                resolve (id, basedir, importOptions) {
                    if (basedir.indexOf('node_modules') >= 0) {
                        // we're inside the node_modules package
                        // simply add _xxx.scss if no ext. specified
                        return convertToScssImport(id);
                    }

                    if (id.indexOf('~') === 0) {
                        const validPath = id.replace('~', '');
                        // we're inside the application dir, but importing from node_modules
                        // add node_modules path
                        // simply add _xxx.scss if no ext. specified
                        // merge with root
                        return convertToScssImport(path.resolve(importOptions.root, 'node_modules', validPath));
                    }

                    if (id.indexOf('..') !== 0 && id.indexOf('/scss/') >= 0) {
                        // we're inside the application dir, but with importing relative path
                        // simply add _xxx.scss if no ext. specified
                        // merge with root
                        return convertToScssImport(path.resolve(importOptions.root, id));
                    }

                    if (id.indexOf('./') === 0 && basedir.indexOf('/scss/')) {
                        // we're inside the application dir, but with importing relative path
                        // simply add _xxx.scss if no ext. specified
                        // merge with basedir (file path)
                        return convertToScssImport(path.resolve(basedir, id));
                    }

                    if (id.indexOf('/scss/') >= 0) {
                        // we're inside the application dir, but with importing relative path
                        // simply add _xxx.scss if no ext. specified
                        // merge with basedir (file path)
                        return convertToScssImport(path.resolve(basedir, id));
                    }

                    return path.resolve(basedir, id);
                },
            },
        ],

        [
            'postcss-nested',
            {
                bubble: [ '@include' ],
                preserveEmpty: true,
            },
        ],
    ];

    return {
        plugins: [
            'lodash',
            [
                'react-css-modules',
                {
                    handleMissingStyleName: 'warn',
                    context: path.resolve(__dirname, 'lib'),
                    generateScopedName: '[local]___[hash:base64:5]',
                    filetypes: {
                        '.scss': {
                            syntax: 'postcss-scss',
                            plugins,
                        },
                    },
                },
            ],

            '@babel/plugin-proposal-function-bind',

            '@babel/plugin-proposal-export-default-from',
            '@babel/plugin-proposal-logical-assignment-operators',
            [ '@babel/plugin-proposal-optional-chaining', { loose: false } ],
            [
                '@babel/plugin-proposal-pipeline-operator',
                { proposal: 'minimal' },
            ],
            [
                '@babel/plugin-proposal-nullish-coalescing-operator',
                { loose: false },
            ],
            '@babel/plugin-proposal-do-expressions',

            [ '@babel/plugin-proposal-decorators', { legacy: true } ],
            '@babel/plugin-proposal-function-sent',
            '@babel/plugin-proposal-export-namespace-from',
            '@babel/plugin-proposal-numeric-separator',
            '@babel/plugin-proposal-throw-expressions',

            '@babel/plugin-syntax-dynamic-import',
            '@babel/plugin-syntax-import-meta',
            [ '@babel/plugin-proposal-class-properties', { loose: true } ],
            '@babel/plugin-proposal-json-strings',
        ],
        presets: [
            [
                '@babel/env',
                {
                    modules: false,
                    useBuiltIns: 'entry',
                },
            ],
            '@babel/react',
        ],
        env: {
            production: {
                plugins: [
                    '@babel/plugin-transform-react-constant-elements',
                    '@babel/plugin-transform-react-inline-elements',
                ],
            },
        },
    };
};

Part of Webpack config

 {
                test: /\.js$/,
                exclude: /node_modules/,
                use: { loader: 'babel-loader' },
            },
            {
                test: /\.scss$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'style-loader',
                    },
                    {
                        loader: 'css-loader',
                        options: {
                            importLoaders: 2,
                            modules: {
                                localIdentName: '[local]___[hash:base64:5]',
                            },
                        },
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            ident: 'postcss',
                            syntax: 'postcss-scss',
                            plugins: [
                                autoprefixer,
                                cssnano(),
                            ],
                        },
                    },
                    {
                        loader: 'sass-loader',
                        options: {
                            includePaths: [
                                bourbon,
                                bourbonNeat,
                            ],
                        },
                    },

                ],
            },
            {
                test: /\.css$/,
                use: [ 'style-loader', 'css-loader' ],
            },
bohdanbirdie commented 4 years ago

@gajus yo sorry for notification I was wondering if you might have some hint for me, it's pretty blocking issue for me and my project :( Thanks!

gajus commented 4 years ago
import styles from '../SocialShareButtons.scss';

console.log(styles);

What is the output of styles?

bohdanbirdie commented 4 years ago

@gajus Styles are present, but I think this is because of css modules in css-loader image

bohdanbirdie commented 4 years ago

Well, actually, -block and disabled should not be there

gajus commented 4 years ago

And if you edit ./node_modules/babel-plugin-react-css-modules/dist/[..] where it interprets style object, do you get the same class names?

If you do not, then your babel/ css-modules is not the same as what is webpack using.

bohdanbirdie commented 4 years ago

@gajus yep, it's different

styleModuleImportMap
 { './SocialShareButtons.scss':
   { disabled: 'disabled___24cg2',
     '-block': '-block___2eo2t',
     'm-social-share-btn': 'm-social-share-btn___osTa1',
     'm-social-share-btn__label': 'm-social-share-btn__label___CaU7R',
     '#{$animation-name}': '_---animation-name-___3JMhz',
     'half-clock': 'half-clock___2oXlO' } }

It's strange because half-clock is an animation name that is not even used here

image

Can the webpack context be a reason for this issue?

gajus commented 4 years ago

I suggest reviewing configuration and to ensure that your configuration is the same on Babel and webpack, and that your css-modules use same (if any) plugins with Babel as they do with webpack.

bohdanbirdie commented 4 years ago

@gajus alright, thanks for the help and for your open-source project!

bohdanbirdie commented 4 years ago
  1. Passing identical context to webpack, css loader and babel-plugin-react-css-modules didn't help
  2. Using the same plugins for babel-plugin-react-css-modules and post-css loader didn't help
  3. Setting cacheDirectory: false for babel-loader didn't help
  4. postcss-import-sync2 resolve function works correctly 100%

If you will have a chance to look again I would really appreciate it

Here is a complete webpack file

const path = require('path');
const webpack = require('webpack');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const bourbon = require('bourbon').includePaths;
const bourbonNeat = require('bourbon-neat').includePaths;
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');

const pkg = require('../package.json');

const libraryName = pkg.name;

module.exports = {
    mode: 'development',
    context: path.resolve(__dirname, '../lib'),
    entry: path.join(process.cwd(), 'lib/index.js'),
    output: {
        path: path.resolve(process.cwd(), 'build'),
        filename: `${libraryName}.js`,
        library: libraryName,
        libraryTarget: 'umd',
        umdNamedDefine: true,
        publicPath: '/build/',
    },
    module: {
        rules: [
            {
                test: /\.stories\.jsx?$/,
                loaders: [ require.resolve('@storybook/addon-storysource/loader') ],
                enforce: 'pre',
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        cacheDirectory: false,
                    },
                },

            },
            {
                test: /\.scss$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'style-loader',
                    },
                    {
                        loader: 'css-loader',
                        options: {
                            importLoaders: 2,
                            modules: {
                                localIdentName: '[local]___[hash:base64:5]',
                                context: path.resolve(__dirname, '../lib'),
                            },
                        },
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            ident: 'postcss',
                            syntax: 'postcss-scss',
                            plugins: [
                                autoprefixer,
                                cssnano(),
                            ],
                        },
                    },
                    {
                        loader: 'sass-loader',
                        options: {
                            includePaths: [
                                bourbon,
                                bourbonNeat,
                            ],
                        },
                    },
                ],
            },
            {
                test: /\.css$/,
                use: [ 'style-loader', 'css-loader' ],
            },
            {
                test: /\.(eot|otf|ttf|woff|woff2)$/,
                use: 'file-loader',
            },
            {
                test: /\.svg$/,
                exclude: /node_modules/,
                loader: 'svg-react-loader',
                query: {
                    classIdPrefix: '[name]___[hash:base64:5]',
                    propsMap: { fillRule: 'fill-rule' },
                },
            },
        ],
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env': { NODE_ENV: JSON.stringify(process.env.NODE_ENV) },
            DEBUG: process.env.NODE_ENV !== 'production',
        }),
        new CircularDependencyPlugin({
            exclude: /node_modules/,
            failOnError: false,
        }),
        new LodashModuleReplacementPlugin({
            shorthands: true,
            collections: true,
        }),
    ],
    resolve: {
        modules: [ 'lib', 'node_modules' ],
        alias: {
            'lodash-es': 'lodash',
        },
        extensions: [
            '.js',
            '.jsx',
            '.react.js',
        ],
        mainFields: [
            'browser',
            'jsnext:main',
            'main',
        ],
    },
    externals: [
        {
            react: 'react',
            informed: 'informed',
            'react-dom': 'react-dom',
            'prop-types': 'prop-types',
            'react-modal': 'react-modal',
            'react-motion': 'react-motion',
            'react-youtube': 'react-youtube',
            'react-router-dom': 'react-router-dom',
        },
    ],
};
gajus commented 4 years ago

Reading your message it sounds that:

postcss-import-sync2 resolve function works correctly 100%

fixed the issue. Am I reading it wrong?

bohdanbirdie commented 4 years ago

@gajus nope, I just clarified that it's not the source of the issue

I'm trying to debug at the moment and stopped at getTokens function inside requireCssModule Seems like PostCSS parser for SCSS doesn't pick up SCSS properly

image

image

bohdanbirdie commented 4 years ago

Looks like I can even completely remove PostCSS from webpack and it would still work as before (but still missing some styles)

bohdanbirdie commented 4 years ago

I slightly updated the requireCssModule.js with the plugin list

const plugins = [
    // ...extraPlugins,

    require('postcss-at-rules-variables'),
    require('postcss-each'),
    require('postcss-simple-vars'),
    require('postcss-nested')({ preserveEmpty: true }),
    Values,
    LocalByDefault,
    ExtractImports,
    new Scope({
      generateScopedName
    }),
    new Parser({
      fetch
    })

And running the only test for SCSS file (bar.scss)

.a {
  background-color: #ffffff;

  &_modified {
    background-color: #000000;
  }

  $sizes: 40px, 50px, 80px;

  @each $size in $sizes {
    .icon-#{$size} {
      font-size: $size;
      height: $size;
      width: $size;
    }
  }
}

Tokens results

{ a: 'bar__a',
      a_modified: 'bar__a_modified',
      'icon-#{sizes}': 'bar__icon---sizes-' } 

Options file

{
  "plugins": [
    [
      "../../../../src",
      {
        "generateScopedName": "[name]__[local]",
        "filetypes": {
          ".scss": {
            "syntax": "postcss-scss"
          }
        }
      }
    ]
  ]
}

I wonder if the library actually supports SCSS or not. No idea where to dig further :(

bohdanbirdie commented 4 years ago

@gajus maybe you will be interested, I made a babel macro being inspired by your work https://github.com/bohdanbirdie/react-css-modules.macro

hinok commented 4 years ago

@bohdanbirdie Did creating your own react-css-modules.macro resolve your issue?

bohdanbirdie commented 4 years ago

@hinok yes, because styles are accessed in the runtime instead of compile-time (no postcss in this case) I tried to increase performance with other tools like memoization

ikeq commented 4 years ago
[
  'babel-plugin-react-css-modules',
  {
    generateScopedName,
    filetypes: {
      '.scss': {
        syntax: 'postcss-scss',
        "plugins": [
          [
            "postcss-nested",
            {
              "preserveEmpty": true
            }
          ]
        ]
      }
    }
  }
]

Worked for me. Note postcss-nested is the key.