Conditional stylesheet in Nuxt SSR depending on a store value - css

I've been surfing StackOverflow and the Nuxt documentation, but I cannot get my head around this one.
So, I need to do RTL. My project is in Nuxt and we use SCSS. I'm setting the htmlAttrs to dir="rtl" conditionally depending on a store getter that tells me if the language is RTL or not. The requirement for this specific task is that a RTL stylesheet should be imported conditionally from the server side also if the country is RTL, so that it overrides the main.scss file.
Now, in nuxtServerInit(), I cannot set the stylesheet in the head, because the route will not direct me to the file, and, most importantly, Webpack won't compile it, as it's outside the regular flow of the application and not imported by main.scss, which is the stylesheet the Nuxt config is pointing to, and which contains all other styles. I realize that I could use a class and use it conditionally in components, but that is not the requirement. The nuxt.config.js file, in the relevant part, looks like this:
css: [
'#/assets/styles/main.scss'
]
There I obviously don't have access to the store.
What I said I tried was this:
if (isRTL) {
service.addEntryToArray('link', {
rel: 'stylesheet',
type: 'text/css',
href: '../assets/styles/main.rtl.scss'
});
}
(We use a service to add things to the head)
I understand that was naive on my part, because Webpack has no say there, so there is no transpilation, and there is no actual asset, which means the route is just wrong, and even if it were right, the browser would not be able to read it.
Any ideas on how this could be achieved at a non-component level? This needs to be part of the Webpack flow, has to be added server-side, and needs to be an import --I cannot just import it regularly and use just a class (again, I'm not stubborn, I'm working on a project, and this is a requirement by the client).
If you need more information, I'll update the post, but I don't know what else to provide.

Related

How to change tailwind-config.js dynamically based on user settings in rails

I have a Rails 6 app set up to use Tailwind CSS with Webpacker similarly to how it's done in this GoRails tutorial.
I want to be able to change the Tailwind defaults dynamically based on the controller and action so that it's very easy for users to "skin" sections of the site by selecting a few options that then dynamically adjust a few of the Tailwind config options. (An example of how this could be used would be users logged into the admin area of the site changing their font family and background color to match their brand.)
I can't just add a stylesheet to the layout based on a conditional because I'd have to override all of the instances where a Tailwind css variable I want to change (like "sans-serif"). That would be a lot of work and brittle to maintain as Tailwind evolves.
It would be ideal if there was a way to dynamically insert choices selected by the user into the Tailwind config file (/javascript/stylesheets/tailwindcss-config.js), but I'm not sure how to do this.
Also is there a better way to do this in Rails when using Tailwind? It seems like there should be some way to use Javascript from the controller to dynamically change the settings in my tailwindcss-config.js (The Tailwind config file is explained here). So, something in that file like this:
theme: {
fontFamily: {
display: ['Gilroy', 'sans-serif'],
body: ['Graphik', 'sans-serif'],
},
What was a font stack hard-coded as a configuration in Tailwind would become this:
theme: {
fontFamily: {
display: DYNAMICALLY INSERTED FONT STACK,
body: ANOTHER DYNAMICALLY INSERTED FONT STACK,
},
How would you do this in Rails? I have that Tailwind config file living at /javascript/stylesheets/tailwindcss-config.js. Is this possible to do with Webpack in rails? Is this even the correct approach to take with Rails 6 using Webpacker + Tailwind?
I have the feeling that we'd be trying to use a 'buildtime' tool for a 'runtime' operation
To directly inject the variable into tailwindcss config file would imply a rebuild of the actual css served to the user, applying the instructions in tailwind config file to the actual content put in app/javascript/css (assuming the setup used in the mentioned video tutorial).
The operation is carried on by webpack, integrated through the webpacker gem.
IMHO, neither webpack nor tailwind were designed with the purpose of rebuilding the assets at runtime, and, even if I'm definitely aware that a universal machine can do anything ;) I wonder where taking this route would take one, mainly in terms of maintainability.
From this link it seems that triggering a rebuild of webpack on a config change is not straightforward.
Here's a somewhat different path to try:
In the <head> section of the application define css variables (more precisely 'css custom properties') for the settings you want your user to access, which can be set and changed dynamically (from js too)
<style>
:root{
--display-font: "<%= display_font_families %>";
--body-font: "<%= body_font_families %>";
--link-color: "<%= link_color %>";
}
</style>
Alternatively you could create app/assets/stylesheets/root.css.erb (the extension is important) and include it in your template before tailwind
Then you should be able to change your tailwindcss config to something like the following:
theme: {
fontFamily: {
display: "var(--display-font)",
body: "var(--body-font)",
},
extend: {
colors: {
link: "var(--link-color)",
},
}
This way we define a dynamic css layout that responds to the value of css variables. The variables and the structure they act on reside on the same logical level, which corresponds to the actual webpage served to the user.
css variables are easily accessible from js, this is one way to have a clean access from rails too
Now let's imagine that the user wants to change the link color (applied to all the links).
In our imaginary settings form, she chooses an arbitrary color (in any css-valid format - the only constraint here is that it must be a valid css value, something you'll need to address with some form of input validation).
We'd likely want
a preview feature (client side/js): without reloading the page the user should be able to apply the new settings temporarily to the page. This can be done with a js call that sets the new value for the variable --link-color
// userSelectedColor is the result of a user's choice,
// say it's "#00FF00"
document.documentElement.style
.setProperty('--link-color', userSelectedColor);
as soon as this value is changed, all the classes previously created by tailwind, and any rule that make use of the variable, will reflect the change, no need to rebuild the css at all.
Please note that our user is not constrained to an arbitrary subset of the possible values, anything that can be accepted by css is fair game. By assigning to the config parameter a css variable, we actually have instructed tailwindcss to specify it in all its classes as a variable value, which now is under our control through css/js ...
We definitely DON'T NEED (nor want) webpack to rebuild the styles
To try to make it clearer, with our color example, in the generated css there will be classes like these - have a look at this link for an explanation of how customizing tailwind theme works
/* GENERATED BY TAILWIND - well, this or something very similar :) */
.text-link {
color: var(--link-color);
}
.bg-link{
background-color: var(--link-color);
}
/* .border-link { ... */
clearly the browser needs to know the value of --link-color (we've defined it in the :root section) and the value itself can be any valid css, but what interests us is that it can be changed anytime, automagically propagating the change to every rule using it, it's a css variable ...
and we'll want a save feature (server side/rails): when the user clicks on 'save', the new settings should be made persistent (saved in db)
this is plainly accomplished (for example) handling the form submit, saving the new value, which will then be pulled from the db to valorize the css variables on the next render of the page
just my 2 cents :) have fun !
As tailwind config file is "js" you can call ajax data then can add it in the config file data to make it dynamic. As we import the theme file in taiwlind config
I was having the same issue today. This worked for me.
# tailwind.config.js
theme: {
fontFamily: {
custom: ['Gilroy', 'sans-serif']
},
and use it like
#sample.html.erb
<span class="font-custom"> Hello Tailwind! </span>

How to solve "violate CSP directive: "default-src 'self'" in Angular 8?

We have an Angular 8 single page web app deployed on the customer server. They set one of the CSP directive to: default-src 'self'. We build the Angular app using ng build --prod like any other Angular applications. After deploying, we get this error:
main-es2015.47b2dcf92b39651610c0.js:1 Refused to apply inline style because it violates the following Content Security Policy directive: "default-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='), or a nonce ('nonce-...') is required to enable inline execution. Note also that 'style-src' was not explicitly set, so 'default-src' is used as a fallback.
Look into the html code on the browser, I see something like this:
As you can see, Angular actually use tag <style> to serve the css (please correct me if I'm wrong). This violates the CSP directive mentioned in the question.
After searching around, I think Angular/React is quite bad at handling this issue, those frameworks are not built with CSP in mind. You can check out Angular github page, there is an open issue for this. Now I'm searching for a solution to overcome this, of course changing CSP policy is not an option because the customers don't want to.
How can I tell Angular not to use tag <style> in production to serve css? I think to make it works we need to set Angular in a way that it will load the css files, and then use styles in those files instead of injecting <style> into html which causes CSP issue.
Edit 1: Our project is using scss.
Edit 2: After searching around, I have found out that Angular will inject your component's styles into the DOM by using <style> element. As shown here:
Now I have an idea, because for each compinent's style will be injeced into the DOM through <style> element, we can prevent this from happening by bundling all component's style .scss file into a single style.scss file. From the image above you can see that we always have an empty <style> element, so if this works, we will endup with only one <style> element and a <link> element that link to our global style scss file. We can have multiple way to remove that empty <style> element before the page got rendered by the browser.
Now I'm stuck at configuring custom webpack to make this happen. We cant use ng eject to get the webpack.config.js file since Angular CLI 6. I've been using Angular CLI 8 so the only way for me to add custom configuration into Webpack is to use custom-webpack npm. I cant find a good config file that has the same output as my desire, please help if you know how to config webpack to bundle all component's styles scss files in Angular into a global scss file.
I think this can be an acceptable answer for my question:
First of all, my recommendation is stay away from using styleUrls. Angular will inject styles of your component into the DOM using <style> element. Secondly, if it's possible, you should know / ask for the CSP policy on the deployment server/environment. What I have been doing to resolve the issue (my project is reletively small with just a couple dozen of components):
Copy (one by one) relative link of components, put them into angular.json, in styles attribute. This is because Angular will bundle all styles in this attribute as a single css/scss file. I copy one by one because the css/scss file was designed to work with Angular View Encapsulation in the first place. Gathering all of them into one place might introduct unexpected issue, this will break the UI. Whenever copy a component style and put into styles, I check if the UI breaks because of that. This will help me narrow down the root cause if such issue happens.
For each component, after copy its component style file's relative path into styles, I remove styleUrls in #Component. This prevents Angular from injecting <style> into the DOM.
Caveats:
Gathering all styles into one single file and load them at once might cause performance issue. Luckily my company project is relatively small.
Now you need to document the new way of making styling work in your project.

Svelte/Sapper Build - Seemingly old CSS still exists after building?

I just committed and pushed a minor CSS tweak. On my server I git pull, npm run build, and forever restart __sapper__/build
Now there seems to be more than one version of the same CSS rule across different files, as per the below screenshot (this is after disabling browser cache):
The correct rule is the third one (vertical-align: top; margin-top: 1px;), which seems to be a combination of CSS files.
Any idea where the 'old' rules are coming from? Cached somewhere somehow?
/EDIT This is my rollup.config.js: https://gist.github.com/Bandit/bbcfd6c70ace5800765313dfe6021854
/EDIT2 The styles in question are in a /style/global.scss file, which is included using the following code in /routes/_layout.svelte:
<style lang="scss" global>
#import "./style/global.scss";
main {
background-color: white;
padding: 5rem 1rem 0 1rem;
}
</style>
Guessing this is somehow the issue? Where is the right place to 'inject' global stylesheet for colours/theme/typography etc?
/EDIT3 The styles being included via _layout.svelte are being included more than once in dev as well, here's a screenshot:
These selectors don't seem to come from a Svelte component, since they're not scoped (e.g. .split-button.svelte-a9ylb1)? Or are you using :global(.split-button) in a Svelte component?
Anyway... I failed to reproduce your issue, but my intuition is that your problem probably comes from the postcss plugin. It has an inject option that is enabled by default. What this option does is injecting a <style> tag in the <head> of your doc; the code that does this is appended to your modules' JS by the postcss plugin. This behaviour might very well clash with what svelte-preprocess or rollup-plugin-svelte is doing.
Try adding inject: false in the 3 places where you're using postcss in your Rollup config, and see if this helps.
Another possibility might be the service worker. I don't think an issue there could produce your result you get, but we never know... You should try options like "Update on reload" and "Bypass for network" (I don't know what are the equivalent options in your browser) to see if that makes a difference.
Otherwise, you may have to show more of your code. Where does this precise CSS rule come from (e.g. style tag in a Svelte component, SCSS file in node_modules, ...)? How is it imported into your project (e.g. import './app.css', #import './app.scss', etc.), and where? Also, I'm surprised that you have the postcss rollup plugin only in the server (the one that is not registered in sveltePreprocess)... What do you need this for, that you don't need on the client?
EDIT: Follow up
Wait, what? You've got some style files under your routes directory?? routes/style/global.scss?
Even with that, I don't appear to be able to reproduce your problem, but it's worth noting that Sapper will try to include every file it encounters under this directory. If you've got a plugin that lets you import *.scss files, then Sapper will actually see a global.scss.js, so it will think it's a server route. Without a plugin that can eat SCSS, it should... crash. If the plugin in question is postcss with its default inject option still to true, to me it looks like a star suspect...
Anyway, some further points of clarification...
svelte-preprocess enables lang="xxx", global attribute in <style global ...>, in .svelte files only.
rollup-plugin-postcss can additionally be added, directly in plugins array (i.e. not as an option of svelte plugin). It gives support for import './foo.scss', in .js files, as well as in the <script> part of .svelte files.
(Of course, SASS support by PostCSS, or PostCSS support by Svelte preprocess are depending on the config you feed them.)
OK. So now there are multiple places where some CSS / SCSS can enter your build. That I can think of, there are the following ways:
<link rel='stylesheet' href='global.css'> in src/template.html: this one will copied as is without processing.
I suppose you can also have such a "custom" <link> tag in the markup (~HTML) part of a .svelte file, and it would be included as is in the resulting HTML (you'd still have the responsibility that the reference CSS file be accessible at the given URL).
import 'something.css' or 'import 'something.scss'in a.jsor JS part of a.sveltefile: these will get processed by bundler & plugins, and converted to some JS code, with optionally additional assets that the JS can reference (typically, a proper CSS file is generated, and some JS code dynamically injects atag for it at runtime; another approach is to generate some JS that will inject every CSS rule in the doc). PostCSS withinject: true` uses the CSS + inject tag method.
the CSS / SCSS style that you write in the <style> part of a .svelte file will also be processed by the Svelte plugin in a similar way as described just before (preprocess option required to accept anything else than raw CSS); depending on the plugin configuration, it may also try to write a '.css' file for your application (see docs. With the emitCss option, that is apparently needed for Sapper, it should output one CSS file per component (or maybe entrypoint).
In your case, you say that you've removed rollup-plugin-postcss from your config, so the 3rd point (import css from js) should not be possible anymore.
Well... I just hope this can help you investigate further.
I've pushed a Sapper + PostCSS example on a branch on this repo. As far as I can tell, it doesn't have the issue you're describing here. So maybe you can find the problem by comparing with what you have. See this commit for the diff with the vanilla official template.
I tried to also add rollup-plugin-postcss, like you initially had in your config, in order to be able to import .scss from outside of Svelte components. But I failed to find a way to do this that don't conflict with Sapper.
EDIT 2
Oh, and just to be sure... Be sure to try a little rm -r __sapper__ && rm -r src/node_modules/#sapper (notice: node_modules under src, not the one in your project's root) before pursuing your investigation. I'm sure you've already done that, but better safe than sorry. Stale things can live in there.

Using two style.css for the same react app

I am trying to use two snippets as components from bootsnipp, and each snippet has its own css. i tried to put them both in the style.css, but it ended up damaging one component for the other to look fine.
I'm thinking about how to use both these styles.css, since in the index.js i can only import style.css.
can i use router to use multiple pages, and import style.css in the second page? but wouldn't that mean i'll have to use the second page as app.js, which is called only once in react? this is kind of confusing me.
EDIT: can I put the css of one component in another css file, and then import it INSIDE that component instead of index.js?
it doesn't bother me by the way whether i put that component inside index.js or not; in fact, I'm not going to use it there.
I would say you need to deal with the global namespace issue. You could create two components with its own css file.
Then add a unique className to stop collisions.
The benefit here is that you could also enable code spitting, so you would only load html/css/js when you need it (see React.lazy).
—-
By trying to load two styles in different times or manners you will still have the same issue of conflicting styles.

Web components and shared styles

This is one of those "what should we do about this"-questions. As you know, web components are supposed to be small, contained applications for websites. However, sometimes these needs to be styled depending on the site they're embedded on.
Example: "Sign up to our newsletter"-component. This component would have a few key items:
An input box
A button
Maybe recaptcha
A method that talks to your service once the button is pressed (passing in the email)
We're going to use Google and YouTube as examples. Google's color scheme is blue (let's imagine that) and YouTube's color scheme is red. The component would then be something like <newsletter-signup></newsletter-signup> on the page you're embedding it in. Both Google and YouTube have this.
The problem comes in, when the component needs to inherit the styles from Google and YouTube. A few deprecated CSS selectors would be great for this, because Google and YouTube's style sheets could simply enable colors for the Shadow DOM, so we wouldn't have to copy/paste the styles. The component should theoretically not know anything about the styles from the host, because we want it to inherit from the host (Google and YouTube).
At the moment, I'm creating a web component using Angular 6, which has a lot of styles, because it has a lot of elements. I'm copy/pasting styles, Bootstrap, icons, and so on from the host site, then styling them based on <newsletter-signup brand="google"></newsletter-signup>. So if the brand is Google, the colors should be red, for example.
This is really bad, because of a few reasons:
Styles have to be updated on both the web component and on the host
Duplicated code is never a good idea
If all the styles are copied 1:1, the amount of bytes required for styles is doubled
How would I, as a developer, take this into account? How do I make styles on the host, then apply them on my web component (call it inheritance)? I'm sure someone has had the exact same problem with Shadow DOM as I am experiencing. Thanks for reading.
I realize you do not want to write same kind of rules for your common component(selector)
i.e. you want to do styling as where your common selector is placed.
Things you can do to handle this:
1. Create your own logical css framework
Write most commonly used CSS rules in global css.For example if you have integrated bootstrap and you want to override bootstrap, you will write most common overrides in app.css which overrides bootstrap.
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles/app.scss"
],
This app.scss should be written in way to which you can override.
Send Rules as input
send custom rules Obj and use in elements you want to override.
<newsletter [input]="customRulesObj"></newsletter>
component.ts
customRulesObj = new CustomRulesClass();
customRulesObj.color = 'red';
You can send rules in input in various component by creating a common class
as you know where you are embedding this component.
Extend this component from a common component
If you are too concerned for css you can extend your component from a common component which provides you with css logic as per need.
export class NewsLetterComponent extends CSSComponent implements OnInit
{
}
css-component.ts
In this component can logically define css as per host, current routerlink and
other multiple if else condition.
You can define rules by switch case conditions and bind those rules to component you have extended.
One of the biggest must-do's of web components is: My host (page where I'm embedding my web component) should not depend on the web component nor know about the web component.
What this basically means: Styles of my web component should not be shared with the host.
If my host decides to update the styles, it should affect my web component. Not the other way around. To solve this, I imported the external styles from my host directly inside the CSS file using #import. Here's an example:
import url("https://my-host.com/styles/core.css");
my-component {
//all styles goes here
}
I did this using SASS but can be done using regular CSS.
This is not a great solution at all, but it does what I want: Inherit the styles from the host. Although I would have to import every stylesheet there is, it still works.
A downside to my solution: When I load the page, it will send a request to the style from the <link> element inside the <head>-tag my host, but also to the style inside my import. So the styles are loaded twice. For our application, which is internal use only, it doesn't matter if we request additional ~200 KB data.
This question is a few years old and the situation has changed. The way to share styles with web components is now to use link tags to a shared stylesheet.
Inside each component:
<link rel="stylesheet" href="https://my-host.com/styles/core.css">
Reference:
https://github.com/WICG/webcomponents/issues/628

Resources