linaria icon indicating copy to clipboard operation
linaria copied to clipboard

:global() CSS selector is not working correctly

Open Roman-Simik opened this issue 4 years ago • 4 comments

Environment

package.json:

{
  "dependencies": {
		"@babel/core": "^7.12.9",
		"@linaria/babel-preset": "^3.0.0-beta.1",
		"@linaria/core": "^3.0.0-beta.1",
		"@linaria/react": "^3.0.0-beta.1",
		"@linaria/shaker": "^3.0.0-beta.1",
		"@linaria/webpack-loader": "^3.0.0-beta.1",
		"@zeit/next-css": "^1.0.1",
		"next": "^10.0.3",
		"react": "^17.0.1",
		"react-dom": "^17.0.1"
  }
}
  • Linaria version: 3.0.0-beta.1
  • Bundler and Node.js: I'm using Next.js 10, so I don't know what they are using for version of the Webpack
  • OS: Windows

Description

So the problem is simple, if I try to create global styles exactly like it is documented here (create only the constant) somewhere in the code and save it, the build throws following error:

Syntax error: Selector "html" is not pure (pure selectors must contain at least one local class or id) image

It says basically that I need to put some class or id selector to the global() function but I don't want to do that, because I don't have any id or class selector on html tag.

This is the custom next.config.js I use to integrate Linaria with Next.js

const LINARIA_EXTENSION = '.linaria.module.css';

function traverse(rules) {
    for (let rule of rules) {
        if (typeof rule.loader === 'string' && rule.loader.includes('css-loader')) {
            if (
                rule.options &&
                rule.options.modules &&
                typeof rule.options.modules.getLocalIdent === 'function'
            ) {
                let nextGetLocalIdent = rule.options.modules.getLocalIdent;
                rule.options.modules.getLocalIdent = (context, _, exportName, options) => {
                    if (context.resourcePath.includes(LINARIA_EXTENSION)) {
                        return exportName;
                    }
                    return nextGetLocalIdent(context, _, exportName, options);
                };
            }
        }
        if (typeof rule.use === 'object') {
            traverse(Array.isArray(rule.use) ? rule.use : [rule.use]);
        }
        if (Array.isArray(rule.oneOf)) {
            traverse(rule.oneOf);
        }
    }
}

module.exports = (nextConfig = {}) => {
    return {
        ...nextConfig,
        webpack(config, options) {
            traverse(config.module.rules);
            config.module.rules.push({
                test: /\.(tsx|ts|js|mjs|jsx)$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: require.resolve('@linaria/webpack-loader'),
                        options: {
                            sourceMap: process.env.NODE_ENV !== 'production',
                            ...(nextConfig.linaria || {}),
                            extension: LINARIA_EXTENSION,
                        },
                    },
                ],
            });

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

Roman-Simik avatar Dec 24 '20 16:12 Roman-Simik

Is there any workaround for this until a fix is released?

cdebotton avatar Feb 06 '21 06:02 cdebotton

Just wanted to add that I debugged this a bit and solved the problem in my case. It's really an issue with that config (which I also lifted from the next-linaria plugin).

This issue is in the naming convention. CSS exported from _app must be global, and because of this, can't have the .linaria.css extension.

I could probably clean it up a bit, but here's the config that I'm using that works perfectly:

const LINARIA_EXTENSION = '.linaria.module.css';

function traverse(rules) {
  for (let rule of rules) {
    if (typeof rule.loader === 'string' && rule.loader.includes('css-loader')) {
      if (
        rule.options &&
        rule.options.modules &&
        typeof rule.options.modules.getLocalIdent === 'function'
      ) {
        let nextGetLocalIdent = rule.options.modules.getLocalIdent;
        rule.options.modules.getLocalIdent = (
          context,
          _,
          exportName,
          options,
        ) => {
          if (context.resourcePath.includes(LINARIA_EXTENSION)) {
            return exportName;
          }
          return nextGetLocalIdent(context, _, exportName, options);
        };
      }
    }
    if (typeof rule.use === 'object') {
      traverse(Array.isArray(rule.use) ? rule.use : [rule.use]);
    }
    if (Array.isArray(rule.oneOf)) {
      traverse(rule.oneOf);
    }
  }
}

module.exports = (nextConfig = {}) => {
  return {
    ...nextConfig,
    webpack(config, options) {
      traverse(config.module.rules);
      config.module.rules.push({
        test: /(?!_app)\.(tsx|ts|js|mjs|jsx)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: require.resolve('@linaria/webpack-loader'),
            options: {
              sourceMap: process.env.NODE_ENV !== 'production',
              ...(nextConfig.linaria || {}),
              extension: LINARIA_EXTENSION,
            },
          },
        ],
      });
      config.module.rules.push({
        test: /_app\.(tsx|ts|js|mjs|jsx)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: require.resolve('@linaria/webpack-loader'),
            options: {
              sourceMap: process.env.NODE_ENV !== 'production',
              ...(nextConfig.linaria || {}),
              extension: '.css',
            },
          },
        ],
      });

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

cdebotton avatar Feb 09 '21 03:02 cdebotton

Very cool, @cdebotton. I just linked the PR to update that plugin to your work. :)

mikestopcontinues avatar Mar 10 '21 11:03 mikestopcontinues

Сan be much easier:

const LINARIA_EXTENSION = '.linaria.module.css';

function traverse(rules) {
  for (let rule of rules) {
    if (typeof rule.loader === 'string' && rule.loader.includes('css-loader')) {
      if (
        rule.options &&
        rule.options.modules &&
        typeof rule.options.modules.getLocalIdent === 'function'
      ) {
        let nextGetLocalIdent = rule.options.modules.getLocalIdent;
        rule.options.modules.mode = 'local';
        rule.options.modules.auto = true;
        rule.options.modules.exportGlobals = true;
        rule.options.modules.exportOnlyLocals = false;
        rule.options.modules.getLocalIdent = (context, _, exportName, options) => {
          if (context.resourcePath.includes(LINARIA_EXTENSION)) {
            return exportName;
          }
          return nextGetLocalIdent(context, _, exportName, options);
        };
      }
    }
    if (typeof rule.use === 'object') {
      traverse(Array.isArray(rule.use) ? rule.use : [rule.use]);
    }
    if (Array.isArray(rule.oneOf)) {
      traverse(rule.oneOf);
    }
  }
}

Just add extra options:

rule.options.modules.mode = 'local';
rule.options.modules.auto = true;
rule.options.modules.exportGlobals = true;
rule.options.modules.exportOnlyLocals = false;

Full documentation mode, auto, exportGlobals and exportOnlyLocals

mdenisov avatar Jun 02 '21 14:06 mdenisov