I have this (shorten for the question) single file component (vue 3.2.31):
<template lang="pug">
.test Hello world!
</template>
<style lang="sass" scoped>
.test
font-weight: bold
</style>
<script setup lang="ts">
onMounted(() => {
console.log('Mounted');
});
</script>
It is bundled via vitejs, exported as (let's say) NamedExport and served on demand as a base64 encoded string to be imported client-side.
const component = await defineAsyncComponent(async () => {
// A module that exports multiple components.
const module = await import(base64StringSentFromTheServer);
// Choose one.
return module['NamedExport']);
})
then, the result is bound to:
<component :is="component" />
It works well, except two things, one of these is that hooks are not called (onMounted in this case), the other being that styles importer is not called either.
Is it an expected behavior, or do I miss something? Is it the <script setup> way of writing the component that is responsible?
It appears that I had two instances of Vue running (one bundled with my package, with rollup, and one imported in the script itself), and for an unknown reason, none of the two was calling hooks.
By removing one of the instances (actually, passing vue as external in rollup build configuration) it now works well.
Related
I have the following code:
blah-foo.vue:
<template>
<div>Hello {{ name }}</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
name: {
type: String,
}
},
});
</script>
<style scoped>
div {
color: white;
}
</style>
and App.vue:
<template>
<blah-foo
name="Alice"
></blah-foo>
<blah-foo
name="Bob"
></blah-foo>
</template>
The result in my browser is the following:
<div data-v-73bdd40c>Hello Alice</div>
<div data-v-73bdd40c>Hello Bob</div>
Is there any way I could tell the vue loader to generate an unique data-v-* attribute for each of them ?
What is happening in production is that since the component blah-foo is called on many different lazy-loaded pages, I end up having many times
div[data-v-73bdd40c] {
color: white;
}
all overriding each other.
That isn't a problem in itself, it just does seem very disgraceful (code wise) after having loaded a few pages when inspecting elements.
That is not possible with vue-loader. And it shouldn't be done anyway.
The whole point of the data-v-xxx attribute is to identify the components in the DOM so vue can apply to them the correct scoped css.
If it ever applied uniq data-v attributes, it would not be able to apply the correct css.
I understand your problem is that, on production, you see in the css inspector several times the css code, right?
My guess is that it's related with sourcemaps, which may mess with the css inspector. But I can't help more without additional details on your project configurations.
Even if your component is used on several pages, its module will be fetched and loaded only once. You don't have the scoped css loaded several times, it's just an inspector problem.
This is about a Vue 3 app with Vite, not webpack.
For now, as you can see from this issue on vite's issue page, vite doesn't have a convenient way of inlining SVGs without using external plugins. Vite does however, support importing files as raw text strings. As such, I had an idea to use this feature and to inline SVG's by passing the raw SVG strings into an element's v-html.
It actually works great, the SVG shows up on the page as expected and I can do the usual CSS transforms (the whole purpose of inlining them like this), but it's not perfect. As it currently stands, the element that receives the v-html directive simply places the provided HTML nested as a child. For example, if I do <span v-html="svgRaw" />, the final HTML comes out something like this
<span>
<svg>
<!-- SVG attributes go here -->
</svg>
</span>
Is there any way for me to essentially replace the parent element on which v-html is declared with the top-level element being passed to it? In the above example, it would mean the <span> just becomes an <svg>
EDIT:
Thanks to tony19 for mentioning custom directives.
My final result looks like this:
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.directive("inline", (element) => {
element.replaceWith(...element.children);
});
app.mount("#app");
Then, in the component I simply use the directive, <svg v-html="svgRaw" v-inline /> and it works great!
You could create a custom directive that replaces the wrapper element with its contents:
Use app.directive() to create a global directive, named v-inline-svg:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App)
.directive('inline-svg', el => {
if (!el) {
return
}
// copy attributes to first child
const content = el.tagName === 'TEMPLATE' ? el.content : el
if (content.children.length === 1) {
;[...el.attributes].forEach((attr) => content.firstChild.setAttribute(attr.name, attr.value))
}
// replace element with content
if (el.tagName === 'TEMPLATE') {
el.replaceWith(el.content)
} else {
el.replaceWith(...el.children)
}
})
.mount('#app')
In your component, include v-inline-svg on the v-html wrapper element (also works on <template> in Vue 3):
<svg v-html="svgRaw" v-inline-svg />
<!-- OR -->
<template v-html="svgRaw" v-inline-svg />
demo
I found that using the method above works but is only good for a single rendering of the svg... The element starts throwing errors if I try to change the svg contents dynamically, not sure why but assuming that the dom replacement has something to do with it.
I modified the code slightly for my use case.
app.directive('inline-svg', {
updated: (element) => {
if (element.children.length === 0) {
return
}
const svg = element.children[0]
if(svg.tagName.toLowerCase() !== 'svg') {
return
}
for (let i = 0; i < svg.attributes.length; i++) {
const attr = svg.attributes.item(i)
element.setAttribute(attr.nodeName, attr.nodeValue)
}
svg.replaceWith(...svg.children)
}
})
In my component I have.
<svg v-if="linkType !== null" v-html="linkType" v-inline-svg></svg>
The directive now copies the svg attributes across from the child to the parent and then replaces the child with it's children.
Coming from Vue2. I think this still works:
Instead of span you can use the special Vue tag template:
<template v-html="svgRaw" />
This will not render <template /> as a tag itself, but render the elements given in v-html without a parent element.
I'm trying to build a simple mail editor in vue (and vuex). Once everything is edited out of some input, i'd like to inline some scss files into the resulting html, using juice.
If i try to import one css using webpack raw-loader,
import css from '!raw-loader!./../assets/sass/test.css';
then i can pass the css value to juice
let result=juice.inlineContent(html,css)
and then injecting with v-html (see below) in one of my component to render the email.
Whitout using the raw-loader, the css will be applied to everything, beeing imported.
If i try to use the raw-loader with a scss file, it's not compiled - rightly - properly.
import css from '!raw-loader!./../assets/sass/main.scss';
I'm quite new to vue and webpack, so, is there something/somewhere i can dig in to understand a way to preprocess a scss file and then pass it as a string to vue, without applying it as style in vue?
I've also tried to scope some css to a component in which i use a v-html tag (and where the inlined html should go)
<template>
<div v-html="render" />
</template>
<script>
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters(["render"]),
},
};
</script>
<style lang="scss" scoped>
table {
border: 1px solid red;
}
</style>
At first in the value (the getters) ther's no table, but when i add it to the component (with the editor) the style it's not rendered at all, as if the component is not re-rendered. That's the reason i decided to inline the css before injecting in the DOM, instead of doing it after some action - like a click on a button.
I've also tried using >>> but with no luck. I know this is not the main question, just a "side quest", but i'm just learning
import css from '!raw-loader!sass-loader!./../assets/sass/main.scss';
It first compile the sass and then import it as a row string... easy peasy
I'm using CSS Modules with Nuxt and have run into some issues when trying to import a stylesheet in my js. If I import my stylesheet directly into the...
<style module>
#import './index.css';
</style>
...everything works as expected. In my particular case I need to run a computed property to choose between two different stylesheets so instead of importing through the <style module> I need to import into <script> and implement the styles like so:
<script>
import foo from './index.css'
export default {
computed: {
styles() {
return foo
}
}
}
</script>
When implementing this on vue everything works great and I get a style object returned. Nuxt however is returning an empty object and none of my styles render correctly.
I'm activating CSS-Modules in my nuxt.config.js file like this:
export default {
...
loaders: {
css: {
modules: true
}
}
...
}
Is this an issue with Nuxt SSR? I've been looking for the root cause/solution but haven't had much luck in my search.
Update
After taking ivandata's advice and adding to my build script this code:
export default {
....
build: {
extend (config, ctx) {
const cssLoader = config.module.rules.find(rule => {
return rule.test.toString() === '/\\.css$/i';
});
delete cssLoader.oneOf[0].resourceQuery;
...
}
}
}
CSS modules appear to be working but a new problem popped up which is that now the project doesn't understand any vue-component styles that are not css-modules. After doing a bit of research I found out that the resourceQuery is telling the loader what type of file to apply the loader options to.
I've tried digging through the style loader on vue.cli 3 and comparing the differences to Nuxt. I removed ivandata's snippit and I tried matching the loaders of vue and nuxt but the problem still persisted.
Here is what is happening visually when between enabling and disabling ivandata's code:
Disabled
Enabled
And here is a code snippet of what is going on in my project:
<template>
<section :class="style.container">
<h1>hey</h1>
<h2 class="test">hey</h2>
</section>
</template>
<script>
import style from './index.css'
export default {
computed: {
style() {
return style
}
}
}
</script>
<style>
h1 {
font-size: 100px;
}
.test {
font-size: 100px;
}
</style>
So as you can see if I have the resourceQuery in the css-loader my javascript import's of css do not work but all vue-component styles worked as normal. Once I remove the resourceQuery the js imported stylesheet works but the vue-template style classes no longer work. I don't think the solution lies in removing resourceQuery and I'm curious if this has something to do with another loader entirely. I've dug quite a bit through the vue.cli 3 loaders and can't see anything that distinctly sticks out to me.
Ok this another way. Leave your old code. Remove my code and add ?module to import file path:
<script>
import foo from './index.css?module'
export default {
computed: {
styles() {
return foo
}
}
}
</script>
I was wrong. resourceQuery option is used to test against the query section of a request string.
You don't need activate css-modules in nuxt, they active by default.
Nuxt.js use vue-style-loader for load styles.
https://vue-loader.vuejs.org/guide/css-modules.html#opt-in-usage
By default, all styles loading from style tag with module attribute, because style loader use resourceQuery /module/ in oneOf rule. So if remove this property nuxt will load styles as you want.
export default {
....
build: {
extend (config, ctx) {
const cssLoader = config.module.rules.find(rule => {
return rule.test.toString() === '/\\.css$/i';
});
delete cssLoader.oneOf[0].resourceQuery;
...
}
}
}
nuxt version: 2.0.0.
You need to do nothing but exchange scoped to module,such as:
<template>
<div :class="$style.red">TEST</div>
</template>
<style module>
.red{color:red;}
</style>
I understand that with 'scoped' i can isolate css stylings to a component, so What's the difference between scoped and module in vuejs components? When should I use module over scoped?
According to the docs:
CSS Modules as an alternative for simulated scoped CSS
So, it's an alternative - It's also worth noting that scoped on a parent component means child components can't see the CSS but with module you can access this.$parent.$style.red to gain access to the styling.
It does however have the added advantage that you can then access your CSS from within your code:
<style module>
.red {
color: red;
}
</style>
<script>
export default {
created () {
console.log(this.$style.red)
}
}
</script>