This article is in response to issue where scoped style is not applied when extending components.
// Comp.vue
<script>
export default {
data: () => ({
text: 'Hello'
})
};
</script>
<template>
<p class="text">
{{ text }}
</p>
</template>
<style scoped>
.text {
background-color: yellow;
padding: 10px;
font-size: 1.3rem;
}
</style>
// ExtendedComp.vue
<script>
import Comp from './Comp.vue';
export default {
extends: Comp,
data: () => ({
text: 'Hello extended'
})
};
</script>
I found that by writing load in the plugins section of vite.config.js, you can modify the SFC before building. I thought if I could bring the style tag of the extended component similarly, it would work, and it did.
Specifically, I defined the following plugin in vite.config.js.
// vite.config.js
import { fileURLToPath, URL } from 'node:url'
import path from 'node:path'
import { readFileSync } from 'fs'
import { parseDocument } from 'htmlparser2'
import { findAll, getElementsByTagName, appendChild, removeElement } from 'domutils'
import { render } from 'dom-serializer';
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue(),
{
async load(id) {
if (id.endsWith('.vue')) {
const source = readFileSync(id).toString();
// It doesn't matter what parses the SFC, but let's use an HTML parser used in vue here.
const dom = parseDocument(source);
const stls = findAll(elem => elem.tagName === 'style' && elem.attribs?.src?.endsWith('.vue'), dom.children);
if (stls.length === 0) {
return source;
}
for (const stl of stls) {
const src = stl.attribs.src;
// If the style tag that references the .vue is left,
// a compile error will occur due to duplicate style tags.
// so remove it here.
removeElement(stl);
const absPath = path.resolve(path.dirname(id), src);
// `resolve.alias` can be resolved with `this.resolve`,
// but relative paths are not resolved, so I do it like this.
const resolved = (await this.resolve(absPath)) || (await this.resolve(src));
const source = readFileSync(resolved.id).toString();
const vueDom = parseDocument(source);
const vueStls = getElementsByTagName('style', vueDom.children);
for (const vueStl of vueStls) {
appendChild(dom, vueStl);
}
}
return render(dom);
}
}
},
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
Then specify the vue file with the style you want to refer to from the extended component.
// ExtendedComp.vue
<script>
import Comp from './Comp.vue';
export default {
extends: Comp,
data: () => ({
text: 'Hello extended'
})
};
</script>
<style src="./Comp.vue"></style>
Note: You can refer to it even without extending.


Top comments (1)
Wouldn't it be easier to move the style to a separate file and connect it by import in both components?