How are `postcss-import` configured plugins applied - css

I've just started using PostCSS exclusively with Webpack. When using postcss-import to inline external stylesheets, I see it's options allow us to configure plugins and transformers to be applied on imported sources, but I'm a bit confused on how this fits in together with other options configured for the main PostCSS runner.
For instance, if I want to inline URLs, should I be adding the postcss-url plugin to postcss-import, the PostCSS runner or both (if my main stylesheet also has URL references)?

It's recommended to make postcss-import the first plugin in your list when you're defining the plugins for postcss in webpack. Since postcss-import just inlines the #import to the start of the file, any postcss plugin defined afterwards will be applied to it.
Example:
(For the example i'm gonna assume you use a postcss.config.js file, the same logic applies if you use an array for the plugins in the webpack 1 format)
// Header.css
#import 'button.css';
.foo {
font-size: 3rem;
transform:translateY(-10px);
}
// Button.css
.bar {
transform:translateX(20px);
}
If the import plugin is behind autoprefixer, it will first apply the autoprefixer plugin on the file and then afterwards import the #import file. So by the time the file is imported the prefixing will have already happened, the output will be:
// postcss.config.js
module.exports = {
plugins: {
'autoprefixer': {},
'postcss-import': {}
},
};
// output.css
.bar {
transform: translateX(20px); // Prefixing hasn't happened on the imported file
}
.foo {
font-size: 3rem;
transform:translateY(-10px);
-webkit-transform:translateY(-10px); // original file has been prefixed though
}
If you put the import first though, it will inline the imported file and then do the autoprefixing, this means both the imported and the original file will be autoprefixed:
// postcss.config.js
module.exports = {
plugins: {
'postcss-import': {},
'autoprefixer': {}
},
};
// output.css
.bar {
transform: translateX(20px);
-webkit-transform:translateX(20px); // Also prefixed now
}
.foo {
font-size: 3rem;
transform:translateY(-10px);
-webkit-transform:translateY(-10px);
}
So this means you don't actually have to add plugins again in the option of the postcss-import plugin.

Related

Selector ":root" is not pure (pure selectors must contain at least one local class or id) - NextJS with SASS modules

I've recently been switching to using modules in my next.js project, but I keep receiving this error in my newly created .module.scss files: "Selector ":root" is not pure (pure selectors must contain at least one local class or id)". I know this is because I'm not using pure css selectors as I've seen elsewhere online, and the only problem is the imports that I'm using, but I need those imports for variables like $cl-light-gray as seen below in this example file:
#import "src/common/styles/global-styles.scss";
#import "node_modules/bootstrap/scss/bootstrap";
#import "src/common/styles/palette.scss";
#import "src/common/styles/typography.scss";
.dashboard-dropdown-hover {
#extend .px-1;
#extend .py-2;
#extend .mt-3;
border: 1px solid transparent;
border-radius: 8px;
transition: 200ms;
background-color: transparent;
}
.dashboard-dropdown-hover:hover {
background-color: $cl-light-gray;
}
Does anyone have a solution to how I should fix this import problem? I know that if I switch back to .scss it will work, but I'm trying to avoid importing all the .scss files in _app.tsx because that would be at least 30 imports and also these styles aren't intended to be global. Lastly, why does Next.js expect me to use pure css selectors when I'm using Sass, which is used because of its non-pure elements?
After scouring the internet for a few hours I found a great solution from here: https://dhanrajsp.me/snippets/customize-css-loader-options-in-nextjs
EDIT: If you're using Next.js 12, check the bottom of the article above, because the solution is a little different.
You'll want to change your next.config.js file to include the following:
/** #type {import('next').NextConfig} */
require("dotenv").config();
const regexEqual = (x, y) => {
return (
x instanceof RegExp &&
y instanceof RegExp &&
x.source === y.source &&
x.global === y.global &&
x.ignoreCase === y.ignoreCase &&
x.multiline === y.multiline
);
};
// Overrides for css-loader plugin
function cssLoaderOptions(modules) {
const { getLocalIdent, ...others } = modules; // Need to delete getLocalIdent else localIdentName doesn't work
return {
...others,
localIdentName: "[hash:base64:6]",
exportLocalsConvention: "camelCaseOnly",
mode: "local",
};
}
module.exports = {
webpack: (config) => {
const oneOf = config.module.rules.find(
(rule) => typeof rule.oneOf === "object"
);
if (oneOf) {
// Find the module which targets *.scss|*.sass files
const moduleSassRule = oneOf.oneOf.find((rule) =>
regexEqual(rule.test, /\.module\.(scss|sass)$/)
);
if (moduleSassRule) {
// Get the config object for css-loader plugin
const cssLoader = moduleSassRule.use.find(({ loader }) =>
loader.includes("css-loader")
);
if (cssLoader) {
cssLoader.options = {
...cssLoader.options,
modules: cssLoaderOptions(cssLoader.options.modules),
};
}
}
}
return config;
},
};
I'm not seasoned with webpack or how it exactly works, but this solution worked for me. You can also change the regex to include css by doing (scss|sass|css) if you want.
As pointed out here, there is another option: you can import those styles in the global.css file. If you do that, Nextjs will be happy.
Any global styles (e.g., :root or any HTML elements/CSS classes that you want to have the same style absolutely everywhere in your app) should be placed into a global CSS file that you import into _app.js (which you just can add to the root folder of your project, if it doesn't already exist).
This global CSS file is also where you want to import any fonts that you will use app-wide.
Step-by-step instructions here: https://nextjs.org/docs/basic-features/built-in-css-support
In my particular case i was having the same headache with that issue, and was because i was trying to import the file with the path:
/node_modules/bootstrap/scss/bootstrap-utilities.scss
and that file was importing another file called _root.scss which was defined a selector in this style.
:root{
}
for solution that error i simply import the specific files used for my requirements
Another resources could help you:
https://www.youtube.com/watch?v=dOnYNEXv9BM&t=1044s
https://sass-lang.com/documentation/modules
https://dev.to/mr_ali3n/use-forward-in-sass-2bab

Use Sass in Saber

Goal
I'd rather enjoy using Sass in the Saber framework, which it supports. Here are the docs for it if you wish. Simple, right?
Problem
You tried to parse SCSS with the standard CSS parser; try again with the postcss-scss parser
My code is extremely vanilla at this stage.
Context
I did this:
yarn add sass-loader sass --dev
And initially did that:
// saber-config.js
module.exports = {
build: {
loaderOptions: {
sass: {
data: `#import "#/scss/main.scss";`
}
}
}
}
...which resulted in no styles. M'kay. So I removed that part and imported the styles directly into the layout component, like so—
<!-- layouts/page.vue -->
<style lang="scss">
#import url('../scss/main.scss');
</style>
That's the point at which the aforementioned error occurs. What's also interesting is that if I move the Sass code from main.scss to the page.vue <style> tags, it works.
Turns out is was a Sass version issue. I resolved it by bumping down a version.
"devDependencies": {
"saber": "^0.11.7",
"saber-plugin-feed": "^0.4.3",
"saber-plugin-query-posts": "^0.4.6",
"sass": "^1.22.12",
"sass-loader": "^8.0.0"
}

Tailwind cdn and isntallation npm not constistant

To quickly start learning how to use tailwind, I added CDN link to my project. Once I had understood the basics, I decided to configure tailwind with webpack.
I created everything from scratch with all the configuration files and pasted the html code from the previous attempt. When I ran the code, it turned out that the pages do not look identical, after configuring some classes are missing and some elements have different property values.
In both cases I use newest version.
Examples:
/* with cdn */
html {
line-height: 1.5;
}
body {
margin: 0;
}
.p-2\.5 {
padding: .625rem;
}
/* with configuration */
html {
line-height: 1;
}
body {
margin: 8px;
}
.p-2\.5 { /* doesn't exist */ }
My tailwind config file look like this, there is rather not many things that I can made wrong:
/* tailwind.config.js */
module.exports = {
future: {
removeDeprecatedGapUtilities: true,
purgeLayersByDefault: true,
},
purge: [
'./templates/**/*.php',
],
theme: {
extend: {},
},
variants: {},
plugins: [],
}
I also tried with file generated by command, but the result was the same - difference in both pages.
npx tailwindcss init --full
Why there are differences between CDN and configuration. Can I somehow configure my project to make it look like the one using CDN?
Using Tailwind via CDN
Before using the CDN build, please note that many of the features that make Tailwind CSS great are not available without incorporating Tailwind into your build process.
You can't customize Tailwind's default theme
You can't use any directives like #apply, #variants, etc.
You can't enable additional variants like group-focus
You can't install third-party plugins
You can't tree-shake unused styles
To get the most out of Tailwind, you really should install it as a PostCSS plugin.

Make webpack conditionally load additional or alternative css file

I'm trying to implement a kind of css theming in an angular 4 project. We use webpack 3 for bundling. The product is intended to be used by several companies and has to look according to their brandbooks. So we need themes.
We gonna have several builds, but we don't want to have several versions of code. All themes should remain in the same codebase. The differences are minimal: colors, icons, fonts — everything may be changed in css.
I have thought of several ways to do it, the most obvious would be to implement theming via :host-context for components and change the class of body by changing environment variable for webpack. With such method we will heve every theme inside our bundle, which is not good. Maybe there's another way?
I wonder if it is possible to have webpack load not the css file it is asked for. Instead it could look for another file by pattern, and if it exists, use that file instead of original one. Or load both files.
For example, we have a button.component.ts which imports button.component.css. If we don't tell webpack to use any theme, it works as usual. But if we do, it tries to read button.component.theme-name.css in the same directory. If that file exists, webpack imports it instead (or altogether with) the default file.
That's basically what I'm trying to do. I guess, the same mechanism would be useful for html templates in angular.
Is there a plugin to do such magic? Or maybe some sophisticated loader option? If you have another way to solve my task — feel free to drop a comment!
I created a loader which can append or replace the content of a loaded file with the content of its sibling which has a chosen theme's title in its name.
TL;DR
Create a file with loader.
Use it in webpack config.
Run webpack in THEME=<themeName> evironment.
theme-loader.js
const fs = require('fs');
const loaderUtils = require('loader-utils');
module.exports = function (mainData) {
const options = loaderUtils.getOptions(this);
let themeName = options.theme;
let mode = options.mode;
if (themeName) {
// default mode
if (!Object.keys(transform).includes(mode)) {
mode = 'replace';
}
// fileName.suffix.ext -> fileName.suffix.themeName.ext
const themeAssetPath = this.resourcePath.replace(/\.([^\.]*)$/, `.${themeName}.$1`);
const callback = this.async();
// for HMR to work
this.addDependency(themeAssetPath);
fs.readFile(themeAssetPath, 'utf8', (err, themeData) => {
if (!err) {
callback(null, transform[mode](mainData, themeData));
} else if (err.code === 'ENOENT') {
// don't worry! if it's not here then it's not needed
callback(null, mainData);
} else {
callback(err);
}
});
} else {
return mainData;
}
};
const transform = {
// concat theme file with main file
concat: (mainData, themeData) => mainData + '\n' + themeData,
// replace main file with theme file
replace: (mainData, themeData) => themeData
};
A piece of sample webpack.config.js to use this handmade loader:
resolveLoader: {
modules: [
paths.libs, // ./node_modules
paths.config // this is where our custom loader sits
]
},
module: {
rules: [
// component styles
{
test: /\.css$/,
include: path.join(paths.src, 'app'),
use: [
'raw-loader',
// search for a themed one and append it to main file if found
{
loader: 'theme-loader',
options: {
theme: process.env.THEME,
mode: 'concat'
}
}
]
},
// angular templates — search for a themed one and use it if found
{
test: /\.html$/,
use: ['raw-loader',
{
loader: 'theme-loader',
options: {
theme: process.env.THEME,
mode: 'replace'
}
}
]
}
]
}
For example, an app.component.css:
:host {
background: #f0f0f0;
color: #333333;
padding: 1rem 2rem;
display: flex;
flex-direction: column;
flex: 1;
justify-content: center;
}
nav {
/* ... */
/* something about nav element */
/* ... */
}
header {
/* ... */
/* pile of styles for header */
/* ... */
}
To implement dark theme we don't need to change all that flex and padding staff and maybe nav and header don't have their own background and font color settings. So we'll just have to override host element style. We create app.component.dark.css:
:host {
background: #222222;
color: #e0e0e0;
}
The we run webpack with environment variable THEME set to dark. The loader takes a request to process app.component.css, tries to load app.component.dark.css and voila! Themed css is appended to the end of resulting file. Because of cascade,
if multiple competing selectors have the same importance and specificity, … later rules will win over earlier rules (MDN).
For HTML we don't have such method. So we'll have to rewrite our template completely. Hopefully, you won't need to do it too often. I my case, I wanted to change like header and footer to fit the cutomer's branding demand.
This was my first attempt to create a webpack loader, please leave a comment if you see a problem with it.

sass converting import to import url

New to sass and grunt. All my vendor related css files are in _vendor.scss.
#import "public/css/bower_components/bootstrap/dist/css/bootstrap.css";
this converted to
main.css
#import url(public/css/bower_components/bootstrap/dist/css/bootstrap.css);
i want it to be
#import "public/css/bower_components/bootstrap/dist/css/bootstrap.css";
instead of url how to do it .
cause next i want to use cssjoin to concatinate all import css into one file and cssmin using grunt.
folder structure
public ---> scss --->vendor-> _vendor.scss
public ---> css ---> bower_components --> ......
public ----> css ---> main.css
grunt tasks
sass: {
dist: {
files: {
'public/css/main.css': 'public/scss/main.scss'
}
}
},
autoprefixer: {
dist: {
options: {
map: true
},
files: {
'public/css/main.css': 'public/css/main.css'
}
}
},
watch: {
css: {
files: '**/*.scss',
tasks: ['sass', 'autoprefixer']
}
},
That's because apparently SASS treats CSS imports differently from SCSS imports. If you #import 'some/file.css', it will be compiled to #import url(some/file.css) because SASS wants to keep your original CSS import intact (while ironically altering it in a standard manner).
However, if you change the extension your CSS files to .scss and import them as such in your main file, the SASS compiler will do its thing and include their contents in place of the original import rule. It simply works because regular CSS syntax is also valid SCSS syntax, and it tricks the compiler into treating your import rule as an SCSS import.
Also, there seems to be no reasonably easy way to alter this behavior and apparently it's not going to change any time soon based on this ticket open since 2012 that's slowly filling up with workarounds for this exact problem.
Hope this helps!

Resources