Different css modules with same class names overwrite each other - css

I'm using webpack and try to use css modules for my theming, for example:
<style lang="scss" module="theme1">
.header {
color: red;
}
</style>
<style lang="scss" module="theme2">
.header {
color: blue;
}
</style>
However, both .header tags get the same localIdentName from css-loader and because of that the second theme overrides the other everytime.
My loader chain: vue-loader, css-loader, sass-loader
My current localIdentName: '[local]_[hash:base64:5]' (neither path nor name result in anything, I just wish there was some sort of [value] tag.

Apparently it's because custom inject names were broken during the v14 -> v15 upgrade from vue-loader.
Heres the issue on GitHub with more details:
https://github.com/vuejs/vue-loader/issues/1578
Temporary solution (put this in the module options of css-loader):
{
localIdentName: '[module]_[local]_[hash:base64:5]',
getLocalIdent(context, localIdentName, localName) {
const { resourceQuery, resourcePath } = context;
const { index, module } = loaderUtils.parseQuery(resourceQuery);
const selector = loaderUtils.interpolateName(context, localIdentName, {
context: context.rootContext,
content: resourcePath + resourceQuery + localName,
})
.replace(/\[local\]/gi, localName)
.replace(/\[module\]/gi, typeof module === 'boolean' ? '' : module)
.replace(/\[index\]/gi, index)
.replace(new RegExp('[^a-zA-Z0-9\\-_\u00A0-\uFFFF]', 'g'), '-')
.replace(/^((-?[0-9])|--)/, '_$1');
return selector;
},
}

Related

How to dynamically load CSS with Angular

For the last couple of days I've been trying several answers, suggestions and tutorials for the problem, but unfortunately non of them did the trick.
The closest one was this:
https://juristr.com/blog/2019/08/dynamically-load-css-angular-cli/
But it uses "extractCss" which has been deprecated since the article has been published.
According to the article:
"styles.js" file should disappear in the Inspector > Network > JS
Clicking the button should add its css file in Inspector > Network > CSS
But neither of these two is happening at the moment.
app.component.ts
const head = this.document.getElementsByTagName('head')[0];
console.log(head);
let themeLink = this.document.getElementById(
'client-theme'
) as HTMLLinkElement;
if (themeLink) {
themeLink.href = styleName;
} else {
const style = this.document.createElement('link');
style.id = 'client-theme';
style.href = `${styleName}`;
head.appendChild(style);
}
}
app.component.html
<head>
</head>
<body>
<button type="button" (click)="loadStyle('client-a-style.css')">STYLE 1</button>
<button type="button" (click)="loadStyle('client-b-style.css')">STYLE 2</button>
</body>
</html>
angular.json
"styles": [
"src/styles.css",
{
"input": "src/client-a-style.css",
"bundleName": "client-a",
"inject": false
},
{
"input": "src/client-b-style.css",
"bundleName": "client-b",
"inject": false
}
These are the main parts of my code.
Hopefully I've explained the problem sufficiently.
Thank you for helping!
You can put your additionals .css in the folder assets (and remove from angular.json)
Then the only change is add the "assets" folder to the href
loadStyle(styleName: string) {
const head = this.document.getElementsByTagName('head')[0];
let themeLink = this.document.getElementById(
'client-theme'
) as HTMLLinkElement;
if (themeLink) {
themeLink.href = `assets/${styleName}`; //<--add assets
} else {
const style = this.document.createElement('link');
style.id = 'client-theme';
style.rel = 'stylesheet';
style.type = 'text/css';
style.href = `assets/${styleName}`; //<--add assets
head.appendChild(style);
}
}
a stackblitz
I think you are missing a property in the link tag, add this to the place you create the link element and it should work.
style.rel = 'stylesheet';
One of possible solution to this task:
import { DOCUMENT } from '#angular/common';
import { Inject, OnDestroy, OnInit, Renderer2 } from '#angular/core';
export class MyComponent implements OnInit, OnDestroy {
private style?: HTMLLinkElement;
constructor(
#Inject(DOCUMENT) private document: Document,
private renderer2: Renderer2,
) {}
public ngOnInit(): void {
const cssPath = '/link/to/style.css';
// Create a link element via Angular's renderer to avoid SSR troubles
this.style = this.renderer2.createElement('link') as HTMLLinkElement;
// Add the style to the head section
this.renderer2.appendChild(this.document.head, this.style);
// Set type of the link item and path to the css file
this.renderer2.setProperty(this.style, 'rel', 'stylesheet');
this.renderer2.setProperty(this.style, 'href', cssPath);
}
public ngOnDestroy(): void {
// Don't forget to remove style after component destroying
this.renderer2.removeChild(this.document.head, this.style);
}
}
If and if your css-files are on the server, so you probably should update your proxy.conf.json file to have access this file from localhost while serve mode is on.

Uncaught DOMException: Failed to set the 'adoptedStyleSheets' property on 'ShadowRoot': Can't adopt non-constructed stylesheets

Hot to use style element in shadowRoot.adoptedStyleSheets?
<style id=style>
h1 { color: red }
</style>
<script>
customElements.define('test-elem', class extends HTMLElement {
constructor () {
super()
const shadow = this.attachShadow({ mode: 'closed' })
shadow.adoptedStyleSheets = [style.sheet]
shadow.append(template.content.cloneNode(true))
}
})
</script>
https://developers.google.com/web/updates/2019/02/constructable-stylesheets
You must create a Constructable Sheet with new CSSStyleSheet()
You can only attach content on an 'open' shadowRoot
<style id="sourcestyle">
h1 {
color: green
}
</style>
<my-element></my-element>
<script>
customElements.define('my-element', class extends HTMLElement {
constructor() {
const sheet = new CSSStyleSheet();
sheet.replace(document.getElementById("sourcestyle").innerHTML);
super() // returns this/element scope
.attachShadow({ mode: 'open' }) // both sets and returns this.shadowRoot
.innerHTML = `<h1>Hello World!</h1>`;
this.shadowRoot.adoptedStyleSheets = [sheet];
}
})
</script>
Maybe better to construct that re-usable stylesheet outside your component,
and without creating a global <style>
const sheet = new CSSStyleSheet();
sheet.replace(`h1{color:green}`);
Without Constructable StyleSheet
(Constructable CSS doesn't work in all Browsers yet)
otherwise you might as well move the <style> with:
(or cloneNode it when you want to re-use it)
this.shadowRoot.append(document.getElementById("sourcestyle"));
Since it is a global sheet, you may want to disable it for the main DOM
<style id="sourcestyle" onload="this.disabled=true">
h1 {
color: green
}
</style>
and then undo the disabled once it is in a shadowDOM:
this.shadowRoot.getElementById("sourcestyle").disabled=false;

Theming with SCSS & nuxt

I'm trying to allow my users to have different themes to choose from however I can't seem to get it working. I've tried multiple different ways, one way is having two different files and i import them based on if the theme is light/dark however once it's imported it stays on the page. Another is within SCSS like this:
$enable-rounded: true;
[theme="dark"] {
$blue: #232c3b;
$body-bg: #262626;
$body-color: white;
}
[theme="light"] {
$body-bg: #ffffff;
$body-color: #000000;
}
#import 'bootstrap/scss/bootstrap.scss';
#import 'bootstrap-vue/src/index.scss';
then in my layouts/default.vue:
import(`~/assets/scss/main.scss`)
export default {
data() {
return {
darkMode: false,
}
},
mounted() {
let bodyElement = document.body;
bodyElement.classList.add("app-background");
let htmlElement = document.documentElement;
let theme = localStorage.getItem("theme");
if (theme === 'dark') {
bodyElement.setAttribute('theme', 'dark')
this.darkMode = true
} else {
bodyElement.setAttribute('theme', 'light');
this.darkMode = false
}
},
watch: {
darkMode: function () {
let htmlElement = document.documentElement;
if (this.darkMode) {
localStorage.setItem("theme", 'dark');
htmlElement.setAttribute('theme', 'dark');
} else {
localStorage.setItem("theme", 'light');
htmlElement.setAttribute('theme', 'light');
}
}
}
}
however nothing happens when I do it this way. I've been trying to figure it out for days and can't seem to get it. It makes me want to just use CSS and avoid SCSS even though I really want to use scss
I think you are importing your scss file in a wrong way. Please try importing it like below out of the script part:
<style lang="scss">
#import '~/assets/scss/main.scss';
</style>
I think you can use this for your project, you can switch into themes or custom on your own
https://color-mode.nuxtjs.org/

How do I parse CSS to prefix CSS selectors

I have the following my-file.css:
.a [data-smth] { ... }
.b .c.d { ... }
.e { ... }
#f { ... }
and I want to do something like the following (pseudocode) in Node.js:
let css = readFile('my-file.css')
let prefixedCss = prefixClasses(css, 'prfx-')
writeFile(prefixedCss, 'my-prefixed-file.css')
to end up with my-prefixed-file.css:
.prfx-a [data-smth] { ... }
.prfx-b .prfx-c.prfx-d { ... }
.prfx-e { ... }
#f { ... }
I have found these npm modules:
https://github.com/vic/prefix-css (hasn't been updated in years and has issues)
https://pegjs.org/ (requires lots of low-level AST configuration)
But I was wondering whether there are better/safer solutions that have already been tested/become standard practice ?
NOTE: The above file contents was just an example. I'm looking for a way to achieve this for files whose content is completely unknown to me. So I'm looking for a "universal" solution.
Any help would be most welcome & thank you in advance! :-)
You might want to check https://github.com/marceloucker/postcss-prefixer#postcss-prefixer.
postcss-prefixer
PostCSS plugin to add a prefix to all css selectors classes and ids.
Usage
With PostCSS cli:
Install postcss-cli and postcss-prefixer on your project directory:
npm install postcss-cli postcss-prefixer --save-dev
and at your package.json
"scripts": {
"postcss": "postcss input.css -u postcss-prefixer -o output.css"
}
Others
postcss([ require('postcss-prefixer')({ /* options */ }) ])
Options
prefix
Type: `string`<br>
Default: none
String to be used as prefix
ignore
Type: `array`<br>
Default: `[]`
Array of selectors to be ignored by the plugin, accepts string and regex.
Example
Example of usage with results generated by the plugin.
Code
const postcss = require('postcss');
const prefixer = require('postcss-prefixer');
const input = fs.readFileSync('path/to/file.css', 'utf-8');
const output = postcss([
prefixer({
prefix: 'prefix-'
ignore: [ /selector-/, '.ignore', '#ignore' ]
})
]).process(input);
Input:
#selector-one .example {
/* content */
}
.selector-two .example2 {
/* content */
}
#ignore .ignore {
/* content */
}
#ignore .other {
/* content */
}
Output:
#selector-one .prefix-example {
/* content */
}
.selector-two .prefix-example2 {
/* content */
}
#ignore .ignore {
/* content */
}
#ignore .prefix-other {
/* content */
}
Credits
Plugin based on postcss-class-prefix create by thompsongl.

Change ion-backdrop opacity?

I'm trying to change the opacity of my ion-backdrop from 0.08 to 0.33.
I've tried:
ion-backdrop {
opacity: 0.33 !important;
}
and setting $popover-ios-background: rgba(0, 0, 0, 0.33);.
Setting the value on ion-backdrop does work but since it's important, it doesn't animate the fade out.
How can I change the opacity of the backdrop?
I know I am a bit late to this party, but now with Ionic 5, you have a CSS selector that will do the job for you. That is mentioned in their documentation as well.
So basically all you could do is, initialize the modal and style it in your SCSS file.
This is my component.ts file:
import { Component } from '#angular/core';
import { ModalController } from '#ionic/angular';
// ModalComponent is just a normal angular component, your path may vary
import { ModalComponent } from '../../modals/modal.component';
#Component({
selector: 'some-component',
templateUrl: './some-component.component.html',
styleUrls: ['./some-component.component.scss']
})
export class SomeComponentComponent {
constructor(
private modalController: ModalController,
) { }
async presentModal() {
const modal = await this.modalController.create({
component: ModalComponent,
cssClass: 'modal-class'
});
return await modal.present();
}
}
and my component.scss file:
.modal-class {
ion-backdrop {
--backdrop-opacity: 0.33;
}
}
I’ve do it using the cssClass property in alertController (Ionic 4)
async alertError(message: string) {
const alert = await this.alertController.create({
cssClass: 'alertClass',
animated: true,
header: 'Error',
message,
buttons: ['OK']
});
await alert.present();
}
ion-alert {
&.alertClass{
background: rgb(0,0,0,.8);
}
}
I am guessing that this ion-backdrop question it's related with the Ionic Alert Controller. If that is the case than you need to apply CSS inside the global.scss (Ionic 3) file or theme\variable.scss (Ionic 4/5). This is required because ion-backdrop lives in the app as an Ionic Global Component.
Therefore find the mentioned file inside your Ionic project. It's usually in this directory app > src > global.scss.
Now let's suppose that we have this Alert Controller instanciated in some page class.
...
async serviceErrorAlert() {
const alert = await this.alertController.create({
cssClass: 'try-again-alert',
...
});
await alert.present();
}
...
As you can see this Alert Controller haves a CSS class of try-again-alert.
So to add all custom CSS that you want just go the style file and add your own style.
global.scss (Ionic 3):
.try-again-alert {
--background: rgba(55, 67, 77, 0.9);
}
theme\variable.scss (Ionic 4/5):
I strongly recommend you to use CSS background attribute and rgba() property. With this approach you can now choose the color that you want (first three numbers) and the opacity of the color (fourth number).
There is currently an open issue about this in Ionic's GitHub. The only workaround listed there that doesn't break the animation is long and complex - too much to list here. A direct link to the solution: https://github.com/ionic-team/ionic/issues/9105#issuecomment-375010398
I only managed to do it in Ionic 5 by using background: rgba() property with a desired alpha value.
Page where modal is called
openModal(): Promise<void> {
return this.modalCtrl.create({
component: ModalPage,
backdropDismiss: true,
cssClass: 'custom-class'
}).then(modal => {
modal.present();
});
}
app/theme/variable.css
.custom-class {
background: rgba(0,0,0,0.8); /*black with 0.8 opacity*/
}

Resources