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 { JSDOM } from 'jsdom'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
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.
// Since no need for window or body, I use a fragment.
const frag = JSDOM.fragment(source)
const stls = frag.querySelectorAll(`style[src$=".vue"]`)
return [...stls].reduce(async (acc, stl) => {
const src = stl.getAttribute('src')
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 frag = JSDOM.fragment(source)
const stls = frag.querySelectorAll(`style`)
// If the style tag that references the .vue is left,
// a compile error will occur.
const regex = new RegExp(`\\s+src=(['"])${src}\\1`)
// It's easier to manipulate the DOM and output innerHTML,
// but when parsing as HTML, self-closing tags aren't resolved well,
// and when parsing as XML, the content of style and script is escaped.
// So I chose to add the styles to the original string.
return [...stls].reduce(
(acc, stl) => acc + stl.outerHTML, acc.replace(regex, '')
)
}, source)
}
},
},
],
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 (0)