How to disable inline CSS in Gatsby? - css

I created a website with gatsby-starter-ghost.
I noticed that by default the CSS is put into the head of every HTML file as an inline style:
<style>.every-thing-is-in-here {}</style>
I want to serve the CSS in its own file and not alongside every HTML file.
How can I disable this behaviour and use <link> for CSS instead?

It seems this is not configurable. I found a solution on Github. Basically in your gatsby-ssr.js rewrite the style elements like this:
export const onPreRenderHTML = ({getHeadComponents}) => {
if (process.env.NODE_ENV !== 'production')
return
getHeadComponents().forEach(el => {
// Remove inline css.
if (el.type === 'style') {
el.type = 'link'
el.props['href'] = el.props['data-href']
el.props['rel'] = 'stylesheet'
el.props['type'] = 'text/css'
delete el.props['data-href']
delete el.props['dangerouslySetInnerHTML']
}
})
}

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

Change css variables dynamically in angular

In my angular project, I have some css variables defined in top level styles.scss file like this. I use these variable at many places to keep the whole theme consistent.
:root {
--theme-color-1: #f7f7f7;
--theme-color-2: #ec4d3b;
--theme-color-3: #ffc107;
--theme-color-4: #686250;
--font-weight: 300
}
How can I update values of these variables dynamically from app.component.ts ? And What is the clean way to do this in angular ?
You can update them using
document.documentElement.style.setProperty('--theme-color-1', '#fff');
If u want to update many values, then create a object
this.styles = [
{ name: 'primary-dark-5', value: "#111" },
{ name: 'primary-dark-7_5', value: "#fff" },
];
this.styles.forEach(data => {
document.documentElement.style.setProperty(`--${data.name}`, data.value);
});
The main thing here is document.documentElement.style.setProperty. This line allows you to access the root element (HTML tag) and assigns/overrides the style values.
Note that the names of the variables should match at both places(css and js files)
if you don't want to use document API, then you can use inline styles on HTML tag directly
const styleObject = {};
this.styles.forEach(data => {
styleObject[`--${data.name}`] = data.value;
});
Then In your template file using ngStyle (https://angular.io/api/common/NgStyle)
Set a collection of style values using an expression that returns
key-value pairs.
<some-element [ngStyle]="objExp">...</some-element>
<html [ngStyle]="styleObject" >...</html> //not sure about quotes syntax
Above methods do the same thing, "Update root element values" but in a different way.
When you used :root, the styles automatically got attached to HTML tag
Starting with Angular v9 you can use the style binding to change a value of a custom property
<app-component-name [style.--theme-color-1="'#CCC'"></app-component-name>
Some examples add variables directly to html tag and it seem in the element source as a long list. I hope this helps to you,
class AppComponent {
private variables=['--my-var: 123;', '--my-second-var: 345;'];
private addAsLink(): void {
const cssVariables = `:root{ ${this.variables.join('')}};
const blob = new Blob([cssVariables]);
const url = URL.createObjectURL(blob);
const cssElement = document.createElement('link');
cssElement.setAttribute('rel', 'stylesheet');
cssElement.setAttribute('type', 'text/css');
cssElement.setAttribute('href', url);
document.head.appendChild(cssElement);
}
}

How to switch css file when using Webpack to load css?

I use gulp to compile my sass file to css files, and reference the css file in my html. The project support theme switch. For example, I have 3 css theme files:
red.css
yellow.css
blue.css
I can currently switch the theme css like this:
var styleDom = $('#theme-style');
var newHref = 'styles/themes/' + themeName + '.css';
if (styleDom.attr('href') !== newHref) {
styleDom.attr('href', newHref);
}
Now I want to use webpack to load the css file.
require('styles/themes/red.css');
It seems work well, but I cannot find a way to switch the theme css file now, does anyone have a solution?
Your approach doesn’t need to change. Just use Extract Text plugin to save out the CSS files. You’ll need to make multiple entry points to create multiple CSS files.
OR
More ideally, (the approach I would take) make your CSS switch based on a different html or body class and just change the class. It won’t add much overhead, and it will be a more ideal UX when changing themes.
You'll need to use a combination of webpacks style-loader and file-loader (second example ) and use require.ensure (second example "dynamic imports") to accomplish this:
function switchTheme(name) {
// Remove the current theme stylesheet
// Note: it is important that your theme css always is the last
// <link/> tag within the <head/>
$('head link[rel="stylesheet"]').last().remove();
// Since webpack needs all filePaths at build-time
// you can't dynamically build the filePath
switch(name) {
case 'red':
// With require.ensure, it is possible to tell webpack
// to only load the module (css) when require is actually called
return require.ensure([], function () {
require('style-loader/url!file-loader!styles/themes/red.css');
});
case 'yellow':
return require.ensure([], function () {
require('style-loader/url!file-loader!styles/themes/yellow.css');
});
case 'blue':
return require.ensure([], function () {
require('style-loader/url!file-loader!styles/themes/blue.css');
});
default:
throw new Error('Unknown theme "' + name + '"');
}
}
Then a call like switchTheme('blue') should do the trick.
And you might have to check your current webpack.config.js, in case you already have configured a loader for .css files.

How to add stylesheet to toolbar

Using the Firefox Addon SDK, I am creating a toolbar with several buttons and I want to create a mouseover effect for the buttons.
At first I thought to use a mouseover event, but then I would have to create a mouseout event to return it to normal, so I figured the best way would be to use css
In my old XUL version of my addon I was able to attach the stylesheet by linking to it in the XUL code and just add css for my #buttonID, which worked perfectly.
But how do I add the css stylesheet for my toolbar using the Addon SDK?
Here's what I've tried so far (which does not produce any errors), but I think this is just for content; if this is correct, then I'm not sure how to bind to the element:
const { browserWindows } = require("sdk/windows");
const { loadSheet } = require("sdk/stylesheet/utils");
//This is how to load an external stylesheet
for(let w of browserWindows){
loadSheet(viewFor(w), "./myStyleSheet.css","author" );
}
I've also tried this:
var Style = require("sdk/stylesheet/style").Style;
let myStyle = Style({source:'./myStyleSheet.css'});
for(let w of browserWindows){
attachTo(myStyle, viewFor(w))
};
And this:
var { attach, detach } = require('sdk/content/mod');
const { browserWindows } = require("sdk/windows");
var { Style } = require('sdk/stylesheet/style');
var stylesheet = Style({
uri: self.data.url('myStyleSheet.css')
});
for(let w of browserWindows){
attach(stylesheet, viewFor(w))
};
And here is my css:
#myButton:hover{list-style-image(url("./icon-16b.png")!important; }
Tested this in Browser Toolbox:
const { require } = Cu.import("resource://gre/modules/commonjs/toolkit/require.js"); // skip this in SDK
const { browserWindows: windows } = require("sdk/windows");
const { viewFor } = require("sdk/view/core");
const { attachTo } = require("sdk/content/mod");
const { Style } = require("sdk/stylesheet/style");
let style = Style({ source: "#my-button{ display: none!important; }" });
// let self = require("sdk/self");
// let style = Style({ uri: self.data.url("style.css") });
for (let w of windows)
attachTo(style, viewFor(w));
The commented part allows to load from a stylesheet file in the addon data directory.
Notice that you need to import SDK loader to use it in the toolbox.
When in an SDK addon, just use require directly.
NB: there is a difference in spelling: self.data.url vs { uri }
See self/data documentation.
NB2: SDK uses a custom widget ID scheme for toggle and action buttons so your button ID might not be what you expect:
const toWidgetId = id =>
('toggle-button--' + addonID.toLowerCase()+ '-' + id).replace(/[^a-z0-9_-]/g, '');
OR
const toWidgetId = id =>
('action-button--' + addonID.toLowerCase()+ '-' + id).replace(/[^a-z0-9_-]/g, '');
using this code, you should be able to use the mouse over or hover to change how it looks.
#buttonID {
//Normal state css here
}
#buttonID:hover {
//insert css stuff here
}
This goes in the javascript file:
const { browserWindows } = require("sdk/windows");
const { viewFor } = require("sdk/view/core");
const { loadSheet } = require("sdk/stylesheet/utils");
const { ActionButton } = require("sdk/ui/button/action");
var StyleUtils = require('sdk/stylesheet/utils');
var myButton = ActionButton({
id: "mybutton",
label: "My Button",
icon: { "16": "./icon-16.png", "32":"./icon-32.png", "64": "./icon-64.png" },
onClick: function(state) {
console.log("mybutton '" + state.label + "' was clicked");
}
});
//this is how you attach the stylesheet to the browser window
function styleWindow(aWindow) {
let domWin = viewFor(aWindow);
StyleUtils.loadSheet(domWin, "chrome://myaddonname/content/myCSSfile.css", "agent");
}
windows.on("open", function(aWindow) {
styleWindow(aWindow);
});
styleWindow(windows.activeWindow);
And here is the css for that
//don't forget to add the .toolbarbutton-icon class at the end
#action-button--mystrippedadonid-mybuttonid .toolbarbutton-icon,{
background-color: green;
}
There are several gotchas here.
First, as of this posting, you should not use capital letters in the id for the button because they get completely removed - only lowercase letters and hyphens are allowed.
The id of the element is not the same as the id you gave it in the button declaration. See below for how to come up with this identifier.
To specify content in the url for the stylesheet file (in the loadSheet function call) you will also need to create a chrome.manifest in the root of your addon folder, and put this in it: content spadmintoolbar data/ where "data" is the name of a real directory in the root folder. I needed a data/ folder so I could load icons for the button declarations, but you need to declare your virtual directories in chrome.manifest which jpm init does not do for you.
How to get the element id for your css file:
The easy way to get the id for your button element for use in an external style sheet is by testing your addon and then using the browser-toolbox's inspector to locate the element, whence you can fetch the id from the outputted code.
However, if you want to figure it yourself, try this formula.
[button-class] = the sdk class for the button. An Action Button becomes action-button
[mybuttonid] = the id you gave the button in the sdk button declaration
[myaddonname] = the name you gave the addon in it's package.json file.
[strippedaddonid] = take the id you assigned the addon in the package.json file, and remove any # symbol or dots and change it to all lowercase.
Now put it all together (don't include the square brackets):
`#[button-class]--[strippedaddonid]-[mybuttonid]]`
An example: action-button--myaddonsomewherecom-mybutton
Really simple isn't it?!
credit for the stylesheet attach code goes to mconley

Dynamic scss variable meteor

I have a scss variable $tint-color that is used in about 100 places.
Once the user logs in, I would like to load a color based on their profile and replace all the usages of $tint-color.
So far I have found two non-ideal solutions:
1) Iterate through all elements and replace the relevant properties.
I am constantly generating new elements -- so this would need to happen repeatedly.
2) Create an override stylesheet, that targets each element.
This will require a lot of duplicate code.
Is there a better / simpler way? I have thought about adding a class to an element in scss, but I am not sure this is possible. Thank you for your help in advance!
What I am doing now, is loading a theme css file after the profile is loaded.
On the server I expose an iron-router route that dynamically replaces any occurrence of the color and returns the theme css.
The issue is that I am not replacing the scss variables, instead I am replacing any occurrence of the color. This is because when the code is executed the .scss files have already been bundled into a .css file on the server.
// return a theme based on the tintColor parameter
this.route('theme', {
where: 'server',
action: function () {
var files = fs.readdirSync('../client');
// find the css file (not the .map file)
var cssFile = _(files).find(function (fileName) {
return fileName.indexOf('.css') > 0 && fileName.indexOf('.map') < 0;
});
var style = fs.readFileSync('../client/' + cssFile, 'utf8');
// remove comments (cannot have them for minification)
style = style.replace(/(?:\/\*(?:[\s\S]*?)\*\/)|(?:([\s;])+\/\/(?:.*)$)/gm, '');
// replace the default tint-color with the dynamic color
style = style.replace(/8cb850/g, this.params.tintColor);
// minify css
if (Settings.isProduction()) {
// from the minifiers package
style = CssTools.minifyCss(style);
}
this.response.writeHead(200, {'Content-Type': 'text/css'});
this.response.end(style);
}
});
Update: I got it to generate with scss variables.
Theme.compile = function (tintColor) {
var dirName = path.dirname(styleFile);
var styles = fs.readFileSync(styleFile, 'utf8');
//replace default theme with dynamic theme
var theme = '$tint-color: #' + tintColor + ';' + '\n';
styles = styles.replace('#import "app/theme.scssimport";', theme);
var options = {
data: styles,
sourceComments: 'map',
includePaths: [dirName] // for #import
};
var css = sass.renderSync(options);
// minify css
if (Settings.isProduction()) {
// remove comments -- cannot have them for minification
css = css.replace(/(?:\/\*(?:[\s\S]*?)\*\/)|(?:([\s;])+\/\/(?:.*)$)/gm, '');
// Use CssTools from the minifiers package
css = CssTools.minifyCss(css);
}
return css;
};
If you do this make sure you add the scss files as assets in the package, example here.
Set a basic $tint-color in your original css.
Then use meteor to send inline CSS with the selected user-tint.
Example:
.tint {
background-color: USER-TINT;
color: USER-TINT;
}
That way you can cache the original css file and save loads of transfer!

Resources