Including partials in components results in duplicate css - vue.js

I'm trying to make use of the #extend of sass so that I don't mix markup and html together. As explained in this article.
In short, instead of writing
<div class="alert alert-primary>This is an alert!</div>
You'd instead write something like
<div class="banner">This is an alert!</div>
.banner {
#extend .alert;
#extend .alert-primary;
}
Such that styling and content stay nicely separated.
The problem: When using this with webpack (sass-loader) and components (e.g. Vue.js or Angular), I run into a problem where including a bootstrap partial will now result in the complete compilation of the entire bootstrap file into css.
This results into a class .btn[data-v-3614b62c] and another .btn[data-v-45ac961c] etc. for every component that uses the partial bootstrap/scss/_buttons.scss and that for all classes defined in that partial.
Even if I don't use it.
In the long run, this will be detrimental for the application since its size will increase rapidly and I image the browser will slow down with that many css classes to parse.
The question(s): How do I make sure sass doesn't duplicate the entire imported partial?
Can I enable some kind of tree shaking where it only includes the classes I use?
Do I have to change my file structure so that sass understands I only need certain classes inside the partial rather than everything?
Code example
This is a vue component using bootstrap
<template>
<form class="form">
<input type="text" class="input">
<button class="button-submit">Send</button>
<button class="button-cancel">Cancel</button>
</form>
</template>
<style lang="scss" scoped>
#import "node_modules/bootstrap/scss/functions";
#import "node_modules/bootstrap/scss/variables";
#import "node_modules/bootstrap/scss/mixins";
#import "node_modules/bootstrap/scss/root";
#import "node_modules/bootstrap/scss/buttons";
.form {
.button-submit {
#extend .btn;
#extend .btn-primary;
}
.button-cancel {
#extend .btn;
#extend .btn-danger;
}
}
</style>
This will result in the entire partial _buttons.scss to be compiled into css instead of only .form .button-submit and .form .button-cancel.
Live example
https://codesandbox.io/embed/musing-feynman-8w2kx.
To see the problem I have:
Right click on the example to the right and click Inspect
In the Elements tab, navigate to #document > html > head
At the bottom you'll have several style elements
Two of them will contain all the button css where only the [data-v-######] attribute is different and at the end are my couple of lines code.
Note that the same happens for production builds. The css is then simply bundled up in a single file, but duplicates are still around.

If you are #importing the same CSS rules into different components, then you will get the same rules duplicated across all modules. That's just how it works.
You should only be #importing modules that define abstract declarations like variables, mixins, functions, etc, not actual styles.
The only way you can de-duplicate the styles globally is if you use something like mini-css-extract-plugin to extract and combine all the CSS into a single file and then run it through something like cssnano which will discard duplicate rules (although with scoped CSS, this probably won't work).
Modules are typically built independently of other modules and there isn't a simple way to know if a rule has been declared already by a previous module. In development you may be using style-loader which operates on a per-module basis and injects styles into the webpage on demand; there's just no way it can work out which styles should be injected in case some particular style has already been injected by another component.
It just gets messy; keep it simple by not duplicating styles in the first place.
If you really want to use #extend, then make a separate .scss file which is the only module that #imports the bootstrap styles, and define all your extensions in there.

Related

Exclude Bootstrap styling from certain routes in Vue.js

I have a single-page vue 2 app made with the cli-tool. Most of my routes use Bootswatch (Bootstrap) styling. But one shouldn't at all. This is only a problem because the Bootstrap affects the body and html styles and generally messes with the other styling. The route shouldn't use Bootstrap gets affected even when I #import the Bootstrap in a scoped <style> only to the routes that should use it. This happends if I first visit the Bootsrap routes and then to the isolated one. How should I go about doing this so that one of my routes is completely isolated when it comes to styling? If it's impossible or very impractical, suggest other ways of doing this. If this weren't a single-page-app this would be easy. But I'd prefer it be one.
I succeeded in encapsulating bootstrap import within a class called 'bootstrap-inside' and assigning it to the #app (Index route for example) div that is supposed to be styled with Bootstrap.
.bootstrap-inside {
#import '~bootstrap/scss/bootstrap.scss';
}
From now on, if you want to use bootstrap, you just have to use .bootstrap-inside in your component/view/layout.
I would suggest creating a view layout for your no-bootstrap pages and set your route to extends that layout (i can give you the solution for this too if you want).
I can mention this answer of another thread about limiting the scope of bootstrap styling in case you go through unexpected bootstrap behavior.
The easiest solution I know for this is to manually reset every css property for a given selector.
You could add an id / class to the root element of your page, and explicitly reset all css properties for all its childs. It would override the default bootstrap styles, but not remplacing its classes though.
Here's a class that would reset every css property: reset css for a div #15901030
It's not super convenient but it should work!

Why is my frontend code behaving differently locally versus production?

Currently just a BootstrapVue VueJS frontend project I have, I have 4 playing cards that I would ideally like to keep on one line/row (as it does locally), but stacks when I view it in production (using Heroku, if it makes a difference)
Currently the code for this is like:
<div
flex-wrap="nowrap"
class="row d-flex nowrap mt-3"
justify-content="space-between"
width="100vw"
>
<b-container>
<b-row>
<b-col>
<PlayingCard/>
</b-col>
....etc for the other cards....
</b-row>
</b-container>
</div>
I've played around a lot with different classes and justify-contents and all that stuff, but continually get different local vs prod results. And I can confirm the code on Heroku is up to date because it redeploys with each new commit and I've added some new features since attempting to fix this styling issue and those appear properly.
Styling issues like that are most commonly due to scoping issues of your CSS. If you inspect the element locally you will likely see that only the local styling has been applied, while if you inspect the element in production, you will see that either the selector contains more CSS (due to identical selectors in two different components), or another selector is applied altogether.
You are getting this problem, because in dev-mode it only loads the CSS of the components you are viewing. In production mode, all the CSS of all components is combined.
To solve the problem, you have several options:
You can use the scoped attribute on your style tag. Vue will automatically add a data-attribute on your component, and scope the styling using that data attribute. This does commonly not work nicely with things like popups that are moved out of their previous location.
<template>
<div>
<!-- something here -->
</div>
</template>
<script>
export default {
//...
}
</script>
<style scoped>
button {
// This only styles the buttons in this component, rather than every button in the application
}
</style>
If you need to style sub-components as well, you can just use a class on your root element and style everything relative to that. You would need to make that class a unique name in your application, but if you just use the name of your component that shouldn't be a problem.
<template>
<div class="comp-card">
<!-- something here -->
</div>
</template>
<script>
export default {
name: 'Card'
}
</script>
<style lang="scss">
.comp-card {
button {
// All buttons that are descendants of our component are now styled.
}
}
</style>
I see two issues with the given code, addressing these may not resolve your issue, but may remove some interference.
Bootstrap provides the class .flex-nowrap for applying flex: nowrap – it should be noted that nowrap is the default behaviour of flexbox, and this may not be needed anyway.
Some attributes in your div appear to be style declarations, and ought to be in the [style] attribute as shown below, or a <style> tag if using SFCs
<div
class="row d-flex mt-3"
style="justify-content: space-between; width: 100vw"
>
Caching of applicable files can vary between local and production environments.
Browsers accessing production servers may cache older versions of files and make it appear that the change was not made. The server may have newer files than what the browser is using.
UI changes in production might be issue of CSS Optimization in build process of vue cli.
Did you try to serve/run build locally if it works then it might issue on production site/cache/browser etc. Try local build with serve or any other tool to verify.
If css optimization issue in build then re-configure build config with underlying tool like webpack or vite etc whichever used.

How to make vue-loader process the same block multiple times, using different loaders

UPDATE
Additional details can be found in the Nuxt feature request that I created.
ORIGINAL QUESTION:
I'm using Nuxt to build a pattern library app. My goal is to display the uncompiled SASS beside each rendered component, like this:
Public-facing .html page:
<div class="my-component"></div>
<pre>
<code>
#import "assets/stylesheets/colors";
.my-component {
color: $some-color;
}
</code>
</pre>
Following this example, I was able to create a custom <docs> language block:
.vue file:
<docs>
#import "assets/stylesheets/colors";
...
</docs>
<style lang="scss">
#import "assets/stylesheets/colors";
...
</style>
This works, but it forces me to duplicate my code. It would be much better if I could eliminate the <docs> block and process the <style> block twice, using two different loaders. Unfortunately, I haven't found a way to do this.
I tried the following, hoping that vue-loader would process both the <docs> block and the <style> block, but only the <docs> block is being processed (probably because the <style> block is nested?), so my SCSS is no longer being compiled and injected into the page:
.vue file:
<docs>
<style lang="scss">
#import "assets/stylesheets/colors";
...
</style>
</docs>
I'm using Nuxt Edge, which includes Webpack 4 and Vue Loader 15.
After reading though the Vue Loader code and hacking around, I don't think it's possible to run a single block through multiple sets of loaders (please correct me if I'm wrong). Nevertheless, I was able to find an adequate solution to my problem:
I ended up using Copy Webpack Plugin to duplicate my .vue files and save them with a .txt extension. I then request them via Axios (on the client side), or use FS to read them from the file system (on the server side). I then extract the <style> and <script> blocks via regex. This isn't ideal, but it works.
You can try seperate css into seperate files like this:
<docs src="/the/path/component.scss"></docs>
<style src="/the/path/component.scss"></styles>
then write a loader to read the file's content and render it.

Can I include scoped css without Vue Loader?

For a project where Vue is dropped in, is using style or similar available to components?
Vue.component('vue-sup', {
template: '<div>Sup</div>',
style: '* { color: blue; }'
})
I tried adding the styles inside the template like:
<div>
<style>
.here{}
</style>
<div>Sup</div>
</div>
which didn't work because the template parser detected a tag with side effects
Vue's implementation of scoped css is entirely a feature of vue-loader, and thus only works with compilation. Scoped css momentarily made a debut into Html 5 but saw almost no adoption and was dropped entirely as far as I know. There is the anticipation that "Shadow DOM" may be supported broadly and could be use to add scoped css, but adoption is not there yet either.
So at this point you can add unique classes or ids obviously to a parent container and scope your css that way, but is understandably not what you are asking for nor is it always practical.
The best alternative is a pollyfill. There are several that are available. Here is one by Sam Thorogood and another by Thomas Park but if you do a quick search you will likely discover more.
I came across the same problem and I'm able to insert styling inside Vue template
by creating a component that will dynamically insert a <style> tag on the DOM. This might be impractical like #skribe said but it allows me to have separate CSS from JS without using .vue extension.
You can take a look at this

Importing CSS and controlling order in <head> using jspm and system.js

I've written the following in an Aurelia app
import "bootstrap/css/bootstrap.css!";
import "./app.css!";
and I want app.css second in since it overrides bootstrap.css styles. However, I'm getting app.css first since I presume the system.js loader is running them in parallel and since app.css is the smaller of the two it gets loaded first.
Is there a way in jspm to define a dependency between these two files to control their loading order is is there some other way?
Many thanks in advance! :)
You could try to import the css using System.import.
E.g. in your index.html:
System.import('bootstrap/css/bootstrap.css!').then(() => {
System.import('./app.css!');
});
But keep in mind that this way you have to make sure that system.js is served with your app. So you can't bundle your whole app as an self-executing bundle.
We have some stuff in the pipeline that should help you with this issue. If you check out this:
<template>
<require from="nav-bar.html"></require>
<require from="bootstrap/css/bootstrap.css"></require>
<nav-bar router.bind="router"></nav-bar>
<div class="page-host">
<router-view></router-view>
</div>
</template>
I know that Aurelia will be passing the CSS files to the loader in order, but I'm not sure if we'll be able to guarantee loading order. Hopefully Rob can come over here and give a proper answer to this, though. I'll point him in this direction.
I had exactly the same problem. Controlling order of CSS is not possible in JSPM. I solved this problem with SASS and some tricks. Here's what I've done:
In html you give main element some id:
<html id="some-id">
Then you create sass file that will host your overrides (_overrides.scss):
#some-id {
#import "buttons";
}
Now your buttons.scss can override styles from bootstrap (_buttons.scss):
.btn-default {
background-color: #B6B3C7;
border-color: #B33A3A;
}
This works thanks to the principle in CSS - most specific selector wins. By wrapping all your customizations in #some-id in scss it will produce code with every bit of code that is imported into curly braces prefixed by #some-id. This way your selector will always be more specific than bootstrap one and will override it.
I don't know if this will be sufficient for you as you don't mention scss, but it was for me.
I've faced similar issue during development.
The code below has helped me solve my problem.
Now everything is loading exactly the way I want it.
System.import('bootstrap/css/bootstrap.css!').then(() => {
System.import('./app.css!');
});
Thanks LazerBass for this suggestion.