I am experimenting with single file .vue components and my first successful build surprised me with the scope of the component style. Generally speaking, I was under the impression that single file components would be self-contained, including the scope of their components.
The component .vue file is
<template>
<div>
Hello {{who}}
</div>
</template>
<script>
module.exports = {
data: function () {
return {
who: "John"
}
}
}
</script>
<style>
div {
color: white;
background-color: blue;
}
</style>
It is built via webpackthough the following webpack.config.js
module.exports = {
entry: './entry.js',
output: {
filename: 'bundle.js'
},
devServer: {
inline: true
},
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader'
}]
}
}
and the entry.js
import Vue from 'vue/dist/vue.js'
import ComponentOne from './component1.vue'
//Vue.component('component-one', ComponentOne)
new Vue({
el: "#time",
data: {
greetings: "bonjour"
},
components: { ComponentOne }
})
The HTML file binding all together is
<!DOCTYPE html>
<html lang="en">
<body>
Greetings:
<div id="time">
{{greetings}}
<component-one></component-one>
</div>
<script src='bundle.js'></script>
</body>
</html>
The rendered result is
The style definitions from component-one for div are also applied to the parent div (with id=time). Is this expected behaviour? Shouldn't the styling be confined to the component?
Note: I can assign an id to the div in my component's template and would therefore contain the styling - my question is about why this behaviour is expected in the context of components self-containement.
The scope of the styling will not be limited to the scope of the component, unless you explicitly mark the style with the scoped attribute:
<style scoped>
div {
color: white;
background-color: blue;
}
</style>
Furthermore, since you are using webpack to create a single bundled file, there would be no way for the browser to separate the styles from one component to the next, since all would be loaded and parsed at the same time.
If you wanted to lessen the footprint of a component on other components, you'd need to both scope your styles and utilize code splitting, although in your case, simply marking the style would be sufficient.
Related
I need to present some random/dynamic html data in my project that might contain style tags, by default vue.js doesn't allow style tags in the template to avoid messing styles that is a very good option.
To make my project work I tried some runtime-component projects on github but they doesn't accept css styles in the way I need, so I came up with the solution of creating my own runtime component with accepting css styles data.
What I want in code is sth like:
<template>
<div v-html="template"></div>
</template>
<script>
export default {
name: "DynamicTemplate",
props: {template: String, style: String}
}
</script>
<style scoped>
// style data goes here
</style>
Any alternative working solution is welcome too :)
I tried v-html and v-text attributes on the component style tag (without any results) and also using css #import statement with base64 encoded css codes (got some errors like Cannot read properties of undefined), but none of them worked :(
You can encapsulate the data inside the style tags inside the component tag provided by vue in this manner ->
<component :is="'style'"> .test-class { color: red; }....Your style data </component>
An example of the same can be found in this project I created
https://codesandbox.io/s/interesting-dewdney-id9erd?file=/src/components/HelloWorld.vue
Edit 1 =>
After reading the comment as I think the CSS scope compilation is done during the build process not during runtime thus having css at runtime won't scope it an alternate solution for this can be embedding your HTML code inside an iframe and passing your code to the iframe using the srcdoc attribute.An example of the same is given below
https://codesandbox.io/s/currying-cherry-zzv3wk?file=/src/components/HelloWorld.vue
<template>
<iframe style="width: 100vw; height: 80vh" :srcdoc="htmlCssCode"></iframe>
</template>
<script>
export default {
data() {
return {
htmlCssCode: `Your Html code here`
<html>
Here is an example of passing that can pass dynamic HTML data in your project with an example using style tags
<template>
<div>
<div v-html="template" :style="style"></div>
</div>
</template>
<script>
export default {
name: 'DynamicTemplate',
props: {
template: {
type: String,
required: true
},
style: {
type: Object,
required: false,
default: () => {}
}
}
}
</script>
Here is the component being used.
<DynamicTemplate
:template="<p>This is some dynamic HTML content</p>"
:style="{ color: 'red', font-size: '14px' }">
</DynamicTemplate>
Here is an example of passing that can pass dynamic HTML data in your project with an example of passing classes dynamically
<template>
<div>
<div v-html="template" :class="classes"></div>
</div>
</template>
<script>
export default {
name: 'DynamicTemplate',
props: {
template: {
type: String,
required: true
},
classes: {
type: Object,
required: false,
default: () => {}
}
}
}
</script>
Here is the component being used.
<DynamicTemplate
:template="template"
:classes="classes"
/>
I am trying to use this library cryptocurrency-icons from Github inside my Nuxt SSR project
This library adds all the svg icons to ./node_modules/cryptocurrency-icons/svg/color directory
I made the following component in the components/BaseCryptoIcon.vue file
<template>
<Component
:is="
require(`~/node_modules/cryptocurrency-icons/svg/color/${name}.svg`)
.default
"
class="BaseIcon"
v-bind="$attrs"
#v-on="$listeners"
/>
</template>
<script>
/**
* https://stackoverflow.com/questions/59148672/how-to-import-multiple-svgs-in-vue-js-via-vue-svg-loader
*/
export default {
name: 'BaseIcon',
// Transparent wrapper component
// https://v2.vuejs.org/v2/guide/components-props.html#Disabling-Attribute-Inheritance
inheritAttrs: false,
props: {
name: {
type: String,
required: true,
},
},
}
</script>
<style>
.BaseIcon {
/* Add some default CSS declaration blocks */
width: 32px;
height: 32px;
}
</style>
When I try to use it in my pages/Index.vue file as following nothing is rendered. It is not giving any error either
<template lang="pug">
base-crypto-icon(name='btc')
</template>
<script lang="javascript">
import BaseCryptoIcon from '~/components/BaseCryptoIcon.vue'
export default {
components: {BaseCryptoIcon}
}
</script>
Can someone kindly tell me how I can make this work in Vue/Nuxt
You can try to make method in components/BaseCryptoIcon.vue:
getIcon(name) {
return require(`~/node_modules/cryptocurrency-icons/svg/color/${name}.svg`).default
}
then in template:
<Component
:is="getIcon(name)"
/>
Probably related question from 2 days ago:
Why image path is not resolved by require() when passed as prop in NuxtJS?
Could someone explain to me what is the difference between importing VUE in the script tag like below...
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
and creating a vue project using the CLI which will connect to the back-end.
Will there be a difference in the production of my Web App or will it be identical?
When will it be recommended for me to use either option?
By using vue-cli you open yourself to many new possibilities - like using webpack to build your application, npm to manage dependencies, and most importantly - Single file components.
Hello.vue (example single file component)
<template>
<p>{{ greeting }} World!</p>
</template>
<script>
import LibraryName from 'library-name' // <- thats how you can import dependencies
export default {
data: function() {
return {
greeting: "Hello"
};
},
methods: {
exampleMethod () {
// do stuff
}
}
}
</script>
<style scoped>
p {
font-size: 2em;
text-align: center;
}
</style>
Some of my single-file components need to take hover color from props.
My solution is that i set css variables in the following way (the main part is in the mounted(){...})
<template>
<div class="btnWrapper" ref="btnWrapper">...</div>
</template>
...
...
props() {
color1: {type: String, default: 'blue'},
},
mounted () {
this.$refs.btnWrapper.style.setProperty('--wrapHoverColor', this.color1)
}
...
...
<style scoped>
.btnWrapper {
--wrapHoverColor: pink;
}
.btnWrapper:hover {
background-color: var(--wrapHoverColor) !important;
}
</style>
This solution seems kind of woowoo.
But maybe there is no better way with pseudo elements, which are hard to control from js.
Do you guys ever take pseudo element's properties from props in vue components?
You have two different ways to do this.
1 - CSS Variables
As you already know, you can create CSS variables from what you want to port from JS to CSS and put them to your root element :style attr on your components created hooks, and then use them inside your CSS codes with var(--x).
<template>
<button :style="style"> Button </button>
</template>
<script>
export default {
props: ['color', 'hovercolor'],
data() {
return {
style: {
'--color': this.color,
'--hovercolor': this.hovercolor,
},
};
}
}
</script>
<style scoped>
button {
background: var(--color);
}
button:hover {
background: var(--hovercolor);
}
</style>
2 - Vue Component Style
vue-component-style is a tiny (~1kb gzipped) mixin to do this internally. When you active that mixin, you can write your entire style section inside of your component object with full access to the component context.
<template>
<button class="$style.button"> Button </button>
</template>
<script>
export default {
props: ['color', 'hovercolor'],
style({ className }) {
return [
className('button', {
background: this.color,
'&:hover': {
background: this.hovercolor,
},
});
];
}
}
</script>
I want to be able to pass data from a object to the <styles> in a single file component. However, it doesn't seem like this is possible.
What I'm trying to achieve:
<template></template>
<script>
export default {
data: function() {
return { color: "#f00" }
}
}
</script>
<style>
body {
background-color: this.color
}
</style>
As far as I'm aware, you are not able to pass data from the component to its stylesheets.
The best practice as far as dynamic styling is to use v-bind:class or v-bind:style if needed. The <style> section should be used for the CSS templating language only.
<template>
<p :style="{ backgroundColor: bgColor }">Lorem ipsum</p>
</template>
<script>
export default {
data() {
return {
bgColor: '#000'
}
}
}
</script>
Let me know if you have any other questions!
Update
Since the goal is to use it for Sass functions like darken, I would recommend managing the various colors needed through utility classes instead.