linaria
linaria copied to clipboard
:global() CSS selector is not working correctly
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)
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;
},
};
};
Is there any workaround for this until a fix is released?
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;
},
};
};
Very cool, @cdebotton. I just linked the PR to update that plugin to your work. :)
С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