How to prevent 'postcss-preset-env' from removing CSS logical properties? - css

When using the following in my Webpack config:
{
test: /\.scss$/i,
use: [
'style-loader',
{
loader: 'css-loader',
options: { importLoaders: 1 },
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
'postcss-preset-env'
],
],
},
},
},
"sass-loader"
],
},
I'm noticing that sometimes CSS rules like the following are removed from the bundled CSS output.
img {
margin-inline-end: 1rem;
}
When removing the above Webpack config, the rule above is applied as expected.
More specifically:
when an [dir="rtl"] attribute is set on the html element, the margin-inline-end: 1rem; style exists
when the dir attribute is not set, the margin-inline-end: 1rem; style does not exist in the bundled output
when the 'postcss-preset-env' plugin is removed, the margin-inline-end: 1rem; style exists regardless of the presence of the [dir] attribute
What is causing this behavior and how can I disable it while continuing to use postcss-preset-env for other things like autoprefixer?

By default postcss-preset-env is processing stage 2+ rules, depending on the exact version of cssdb was installed, you might need to tweak the stage option while using postcss-preset-env to a higher value like stage: 3.
And looks like some logical properties like margin-inline-end will be processed into
[dir='ltr'] { margin-right: 1rem } ...
Also rules can also be disabled precisely in the option :
{
/* use stage 2 features + disable logical properties and values rule */
stage: 2,
features: {
'logical-properties-and-values': false
}
}
This github repo vanilla-js-prototype-starter has webpack & PostCSS configured working well, check and see if it can help a bit.

Related

Set Global Styles Without Class Hashing in Nuxt

I have numerous Vue SPAs in a monorepo that all share a common set of global styles, each SPA and the styles are their own package.json workspace. I'm trying to replace one of them with Nuxt.
The global styles are .scss files, they import Vue bootstrap and have some custom variables and classes.
As such, I did a fresh install of Nuxt and then ran:
yarn add -D sass sass-loader#10 fibers
I know I can get global styles like so:
//in nuxt.config.js:
css: [resolve(__dirname+'/../common/styles/index.scss')
Really I thought that should/would be it, and I see it does get injected into the page. However, the class names are hashed, so it doesn't apply to my components.
Instead of this (fake css to test if it goes in the page):
.test{
text-align: test;
top: test;
}
I get this:
.olAmdkaWN_JnK1npjbKiI {
text-align: test;
top: test;
}
How can I stop the global styles from being hashed like this, especially when I may be importing components from the other SPAs/common and their classnames aren't being hashed in the HTML? Only the injected global styles are getting hashed like this.
I've tried various attempts at setting the localIdentName such as:
//in nuxt.config.js
build: {
extend(config) {
config.module.rules.push({
test: /\.scss$/,
use: [{
loader: 'css-loader',
options: {
modules: false
/*
or sometimes I'll try something like:
modules:{
localIdentName: '[local]'
}
*/
}
},
{
loader: 'sass-loader'
}
]
})
},
I've also set:
cssModules: {
localIdentName: '[local]'
},
Again in the nuxt.config.js. But nothing works and furthermore I think I must have a conceptual error about how global styles are meant to work, as I feel like I'm fighting the framework rather than working with it.
My nuxt, webpack and sass-loader verisons are as follows:
nuxt#2.15.4
webpack#4.46.0
sass-loader#10.1.1 (It was at 7.1.x but the console suggested upgrading it - didn't make a difference in terms of solving this)
package.json:
"dependencies": {
"core-js": "^3.9.1",
"common": "1.0.0", (local dependency)
"nuxt": "^2.15.3"
},
"devDependencies": {
"fibers": "^5.0.0",
"sass": "^1.32.11",
"sass-loader": "10"
}
Turns out all I needed was this (the key was to put it in loaders within build):
//in nuxt.config.js
build: {
loaders: {
cssModules: {
localIdentName: '[local]'
},
},
}
Please note this only works if you properly install your dependencies and heed build warnings in regards to css-loader and sass-loader. I tried downgrading sass-loader and this didn't work until I put it back at "10" which is what Nuxt expected (threw a warning).

webpack 4: Create multiple theme css files

I need to create multiple theme CSS files using webpack version 4 and "mini CSS extract plugin" in my react project. Depends on a place where webpack will find an import of the SCSS file, it should use loader twice - with different data in sass-loader options.
I found nothing useful in the Internet according this goal. I also have already tried to use such webpack's loaders as: webpack-combine-loaders, multi-loader etc...
here is a part of webpack config
module: {
rules: [
{
test: /\.scss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
{
loader: "sass-loader",
options: {
data: '$theme: dark;',
}
},
],
},
{ // the same except data in options
test: /\.scss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
{
loader: "sass-loader",
options: {
data: '$theme: white;',
}
},
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'client.white.css',
}),
new MiniCssExtractPlugin({
filename: 'client.dark.css',
}),
],
and in my scss file (button.scss) I use such condition:
$background: #06cc1a;
$color: white;
#if $theme == dark {
$background: white;
$color: black;
}
.button {
background-color: $background;
color: $color;
}
}
as a result, I want to get two CSS files client.white.css where were applied sass variables for the white theme and client.dark.css where were applied variables for the dark theme
We solved this in our project by using multiple entry points, one for each theme, e.g:
entry: {
light: './src/css/light.scss',
dark: './src/css/dark.scss'
}
With the contents of light.scss files being as follows:
$background: #001560;
#import "~base/scss/base.scss";
Webpack will output a single css file for each theme containing all the styles, both base and theme-specific, which is great when optimising for production.
Note though that you will also get a redundant JS file, which you may want to clean up post-build.
I worked on a web app which use multi theme, and we tackle the problem by saving each theme's colors to backend, so we can get the value from API depending from query, and for styling, we use styled-components for that.
I find css-in-js is really useful in this kind of problem. We actually use both styled components and LESS css for our styling. styled-components are used for coloring based on theme, and the rest is on LESS css. Perhaps you can try to use that too, or even a inline css should do the work since JS variable would work on that.
A specific example is to build a ThemeProvider component that engulf the whole application as its child, ThemeProvider will contain the declaration of class with the use of styled-components and that class can be reused throughout application scope.

Loading an svg in webpack with svg-url-loader

I'm having a lot of trouble working with SVG in my webpack workflow. I'm trying to get it to display with the background: url(sample.svg) property in CSS. Using this alone did not work, so I figured I had use a loader. Here are the steps I used.
I used svg-url-loader to load the SVG.
1.
I installed svg-url-loader via npm and added this to my module.exports:
{
test: /\.svg/,
use: {
loader: 'svg-url-loader'
}
},
2.
I added this to the top of my index.js file:
require('svg-url-loader!./images/topography.svg');
3.
I added background-image with the SVG path to my CSS:
body {
background-image: url("../images/topography.svg");
background-size: 340px, auto;
min-height: calc(100vh - 100px);
margin: 50px;
background-attachment: fixed;
letter-spacing: -1px;
}
4. The SVG is not being rendered to the page. When I inspect the body in browser, I find this:
background: url(data:image/svg+xml,module.exports = __webpack_public_path__ + '8dccca4….svg';);
I don't know too much about data-uri, so maybe I am running into the issue there.
Also, I've tried this using different SVG files, and none of them worked.
I met the same exact error. After some investigation I found I added another svg loader which caused this problem, so I fixed it by deleting the other svg loader:
{
test: /\.svg/,
use: {
loader: 'svg-url-loader'
}
},
{
test: /\.svg$/,
use: [
"babel-loader",
{
loader: "react-svg-loader",
options: {
svgo: {
plugins: [{ removeTitle: false }],
floatPrecision: 2
},
jsx: true
}
}
]
}
So you maybe also added some extra loaders to handle the svg files at the same time, please check.
You can:
a) set up loaders in webpack.config.js:
example.js:
import ExampleIcon from 'assets/icons/example-icon.svg';
...
<ExampleIcon className={styles.exampleIcon} />
webpack.config.js:
{
test: /\.svg$/,
use: [
{
loader: 'babel-loader',
},
{
loader: 'react-svg-loader',
options: {
svgo: {
plugins: [{ removeTitle: false }],
floatPrecision: 2
},
jsx: true
}
}
]
},
b) or set up loaders in the import string:
import ExampleIcon from '!babel-loader!react-svg-loader!assets/icons/example-icon.svg';
...
<ExampleIcon className={styles.exampleIcon} />
I met the same problem too. We have a custom url-loader which is based on url-loader and file-loader. When the size of svg is limited to 10Kb, it will call the url-loader to process the svg,otherwise it will call the file-loader to process. It seems ok,but the bundled file shows that it was processed twice by different loaders. The base64 encoded string was exported through module.exports, but in the page the path was not replaced. This is because I used vue-cli to create project, and the svg was processed by the file-loader. When I deleted the default configuration of file-loader, it worked as expected.
I had the same problem as you. Updating my file-loader from 2.x.x to the latest version fixed the issue.

webpack: understanding source maps when working with CSS

Introduction
I have already setup bundling for my Javascript files with webpack in my project. Now I am in the process of adding CSS files to the webpack configuration. So far, I have been including the CSS files manually in the HTML header by adding <link> elements for every CSS file I depend on (e.g. bootstrap, my own css, etc.). Obviously this is not very elegant and using webpack would be much better, so I would like to replace the link elements and bundle them via webpack.
This should be easy, everything is pretty much documented in the webpack documentation. After reading the documentation and experimenting a bit with webpack I have arrived at the configuration below which already works.
Problem
The problem with my current setup is that I would like to have proper source map support and that does not seem to work. By proper, I mean that I expect that when I run a development build with webpack and I inspect some element in Chrome DevTools, that I will see from which file and which line in the file a certain CSS class originated and that I can click on the CSS rules and the browser jumps to that file.
I do not want to have inline styles in the head element, because then the browser will show something like .foobar { <style>..</style>, rather then .foobar { app.css:154.
With my current setup I have all CSS files combined (but not minified) into one app.css file. This means that if I inspect a bootstrap class such as .btn then it appears as .btn { app.css:3003. However, what I want to achieve is that the browser shows it as .btn { bootstrap.css:3003.
So now I am trying to understand how webpack and the different plugins such as css-loader and min-css-extract-plugin apply CSS source maps, and how I can configure them to achieve a proper debugging experience.
I am not sure how relevant this is, but when I navigate in DevTools under Sources to webpack://./bootstrap/dist/css/bootstrap.css I see that it only contains a single line:
// extracted by mini-css-extract-plugin.
Webpack Setup
index.js:
window.jQuery = require('jquery/dist/jquery');
require('bootstrap/dist/css/bootstrap.css');
require('bootstrap/dist/js/bootstrap');
/* other dependencies */
webpack.config.js:
const devMode = process.env.NODE_ENV !== 'production';
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module: {
rules: [
{ /* Javascript rules excluded */ },
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
{
loader: 'css-loader',
options: {
sourceMap: true
}
}
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
plugins: [
new UglifyJSPlugin (),
new HtmlWebpackPlugin({
template: 'app/index.tpl.html'
}),
new MiniCssExtractPlugin({ filename: devMode ?
'[name].css' :
'[name].[hash].css'
})
],
Conclusion
It seems I just passed the rubber duck test. While I was writing this I arrived at a solution. I will still publish the question, maybe it can help others.
The problem was that I was also using the mini-css-extract-plugin for development and not just for production. I thought that I needed to do that, because when at first I was using the style-loaded I would get styles included in the header and the browser would show me all styles as .foobar { <style>..</style>.
However, the actual problem seemed to be, that I was not using devtools. So the solution was to add devtool: devMode ? 'cheap-module-eval-source-map' : 'source-map', to the webpack configuration to conditionally use the style-loader plugin during development builds and mini-css-extract-plugin during production builds.
webpack.config.js
{
test: /\.css$/,
use: [
{
- loader: MiniCssExtractPlugin.loader,
+ loader: devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
},
{
loader: 'css-loader',
options: {
sourceMap: true
}
}
]
},
/* ... */
+ devtool: devMode ? 'cheap-module-eval-source-map' : 'source-map',

How to remove unused CSS using webpack 4?

I am having problem removing unused CSS in webpack 4. It seems that most of the CSS purification plugins are dependent on extract text plugin which is not updated for version 4.
Here's my commands:
node_modules/.bin/webpack --mode=development --env.operation=separateCSS
OR
node_modules/.bin/webpack --mode=development --env.operation=bundleCSS
Here's part of my webpack.config.js:
rules: [
// Loader for SASS files
{
test: /\.s[ac]ss$/,
use: [
// Use IIFE to choose how to emit the CSS: 1. Extract as separate file 2: Bundle with the JS file
(() => {
return env.operation == "separateCSS" ? MiniCssExtractPlugin.loader : 'style-loader';
})(),
{
loader: 'css-loader',
options: {
importLoaders: 1,
url: true
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
// Write future-proof CSS and forget old preprocessor specific syntax.
// Use the latest CSS syntax today with cssnext.
// It transforms CSS specs into more compatible CSS so you don’t need to wait for browser support.
// It parses CSS and add vendor prefixes to CSS rules using values from Can I Use.
// https://github.com/MoOx/postcss-cssnext
require('postcss-cssnext')()
]
}
},
'sass-loader'
]
}
],
plugins: [
new MiniCssExtractPlugin({
filename: "../css/[name].css"
})
]
Does anybody know how can I modify my config file so webpack can remove unused CSS?
Have you considered using something called uncss. There's a webpack plugin for it. It will look through all your CSS and compare it to your HTML, and remove anything that you're not using.
Take a look: WebPack UnCSS

Resources