🍣
Vue SFCのstyleタグから別のvueファイルのstyleを参照する
元々はコンポーネントをextendするとscoped styleが適用されない問題の解決を試みた所、
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>
vite.config.jsのpluginsにloadを書けばビルド前のSFCを弄れる事を知り、大体似たような感じでextend元のコンポーネントのstyleタグを持って来れれば解決するなーと思って書いたら上手く行った。
具体的にはvite.config.js
に以下のようなpluginを自前で定義し、
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()
// SFCをパースできればなんでも良い。
// windowやbodyは別にいらないので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はthis.resolveで解決するが相対パスは解決してくれないのでこうする
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`)
// .vueを参照しているstyleタグは消しておかないとコンパイルエラーになる
const regex = new RegExp(`\\s+src=(['"])${src}\\1`)
// DOMをmanipulateしてinnerHTML吐いた方がラクなのだが
// HTMLとしてパースするとself-closingタグが上手く解決されず、
// xmlとしてパースするとstyleやscriptの中身がエスケープされてしまうので
// 元のstringにstyleを書き足すようにした
return [...stls].reduce(
(acc, stl) => acc + stl.outerHTML, acc.replace(regex, '')
)
}, source)
}
},
},
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
extendしたコンポーネントから参照したいstyleを持つvueファイルを指定する
ExtendedComp.vue
<script>
import Comp from './Comp.vue';
export default {
extends: Comp,
data: () => ({
text: 'Hello extended'
})
};
</script>
<style src="./Comp.vue"></style>
※別にextendしなくても参照出来る
Discussion