背景
vue-cliを使ったVueコンポーネントのweb-component化は、以下のようなpackage.jsonの書き換えで簡単に実現できたのですが、
"scripts": {
"build": "vue-cli-service build --target wc --name hoge-component",
},
UIフレームワークとしてVuetifyを使ったコードなどで、スタイルが適用されない問題の壁を越えられず。。
仕方ないので、Vueコンポーネントのラッパーライブラリvue-custom-elementsを試してみました。
vue-custom-element
Vueコンポーネントをカスタム要素化してくれるライブラリらしいです。
最初、vue-cliで作ったプロジェクトに対して、ライブラリを適用しようとしてみましたが、カスタム要素化された状態の成果物を得ることができず、断念。
初心にかえって(?)、シンプルなVue環境で試した見たところ、それっぽい成果物を得ることが出来ました。
(参考) 最小構成のvue.jsの環境をvue cli使わずに構築する
https://qiita.com/ctoshiki/items/8e5b6e000adec5b332cb
vue-custom-elementを使ったサンプル
以下の3つのサンプルを作ってみました。
- シンプルなVueのComponentをカスタム要素化したサンプル
- Element-UIを使っているComponentをカスタム要素化したサンプル
- Vuetifyを使っているComponentをカスタム要素化したサンプル
シンプルなVueのComponentをカスタム要素化したサンプル
作ったサンプル
Note
下記のサイト参考にさせていただいて、問題なく作成できました。 (ボタンコンポーネント)
https://qiita.com/doutori/items/09e36b7efa8212ba7044
import Vue from "vue";
import vueCustomElement from "vue-custom-element";
import Button from "./button.vue";
Vue.config.ignoredElements = ["vce-button"];
Vue.use(vueCustomElement);
Vue.customElement("vce-button", Button, {
// shadowDOM化すると中のCSSが効かず、ここで指定したCSSが効く
shadow: true,
shadowCss: `
button {
font-size: 8px;
cursor: pointer;
color: magenta;
padding: 1em 1em;
background: PaleGreen;
`,
});
<template>
<div>
<button>{{ text }} {{ message }}</button>
</div>
</template>
<script>
export default {
name: "button",
props: {
message: String,
},
data() {
return {
text: "button",
};
},
};
</script>
Element-UIを使っているComponentをカスタム要素化したサンプル
作ったサンプル
Note
本家GitHubのコードにあるサンプルを見ながら作成。 (テーブルコンポーネント)
https://github.com/karol-f/vue-custom-element/tree/master/demo/components
注意点としては、カスタム要素化する際に、「shadow: true」を指定すると、CSSが効かなくなりスタイルが崩れることぐらいでしょうか。
import Vue from "vue";
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";
import vueCustomElement from "vue-custom-element";
import Table from "./table.vue";
Vue.use(vueCustomElement);
Vue.use(ElementUI);
Vue.config.ignoredElements = ["vce-table"];
Vue.customElement("vce-table", Table); // shadowDOM化すると中のCSSが効かない
<template>
<div class="card card--primary">
<h4>{{ message }}</h4>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="prop" label="Prop name"> </el-table-column>
<el-table-column prop="value" label="Value">
<template slot-scope="scope">
<div slot="reference">
<strong>{{ scope.row.value }}</strong>
</div>
</template>
</el-table-column>
<el-table-column prop="type" label="typeof">
<template slot-scope="scope">
<div slot="reference">
<el-tag type="gray">{{ scope.row.type }}</el-tag>
</div>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
props: {
prop1: {
required: true,
},
prop2: {},
prop3: {},
stringProp: {
type: String,
},
booleanProp: {
type: Boolean,
},
numberProp: {
type: Number,
},
longPropName: {},
objectProp: {},
arrayProp: {},
},
data() {
return {
message: "Custom Element By Vue + Element UI",
};
},
computed: {
tableData() {
const data = [
{
prop: "prop1",
value: JSON.stringify(this.prop1),
type: typeof this.prop1,
},
{
prop: "prop2",
value: JSON.stringify(this.prop2),
type: typeof this.prop2,
},
{
prop: "prop3",
value: JSON.stringify(this.prop3),
type: typeof this.prop3,
},
{
prop: "stringProp (type: String)",
value: this.stringProp,
type: typeof this.stringProp,
},
{
prop: "booleanProp (type: Boolean)",
value: this.booleanProp,
type: typeof this.booleanProp,
},
{
prop: "numberProp (type: Number)",
value: this.numberProp,
type: typeof this.numberProp,
},
{
prop: "long-prop-name",
value: JSON.stringify(this.longPropName),
type: typeof this.longPropName,
},
];
this.objectProp &&
data.push({
prop: "objectProp",
value: this.objectProp,
type: typeof this.objectProp,
});
this.arrayProp &&
data.push({
prop: "arrayProp",
value: this.arrayProp,
type: typeof this.arrayProp,
});
return data;
},
},
created() {
/* eslint-disable no-console */
console.log("demo-basic created()");
},
};
</script>
※中身は本家サンプルのコピー
Vuetifyを使っているComponentをカスタム要素化したサンプル
作ったサンプル
Note
VueコンポーネントはVuetifyのUIコンポーネント(treeview)のサンプルコードを参考に作成 (ツリーコンポーネント)
https://vuetifyjs.com/ja/components/treeview/
import Vue from "vue";
import Vuetify from "vuetify";
import vueCustomElement from "vue-custom-element";
import Tree from "./tree.vue";
Vue.use(Vuetify);
Vue.use(vueCustomElement);
Vue.config.ignoredElements = ["vce-tree"];
Vue.customElement("vce-tree", Tree); // shadowDOM化すると中のCSSが効かない
<template>
<div id="app">
<v-app id="inspire">
<v-treeview selectable color="warning" :items="items"></v-treeview>
</v-app>
</div>
</template>
<script>
// vuetifyやcssは,、index.tsではなく、ここでimportしないと正しく適用されない模様。
// "vuetify"ではなく"vuetify/lib"にしないと、デザインが微妙に異なる
import Vuetify from "vuetify/lib";
import "material-design-icons-iconfont/dist/material-design-icons.css";
export default {
el: "#app",
vuetify: new Vuetify({
icons: {
iconfont: "md",
},
}),
data: () => ({
items: [
{
id: 1,
name: "Applications :",
children: [
{ id: 2, name: "Calendar : app" },
{ id: 3, name: "Chrome : app" },
{ id: 4, name: "Webstorm : app" },
],
},
{
id: 5,
name: "Documents :",
children: [
{
id: 6,
name: "vuetify :",
children: [
{
id: 7,
name: "src :",
children: [
{ id: 8, name: "index : ts" },
{ id: 9, name: "bootstrap : ts" },
],
},
],
},
{
id: 10,
name: "material2 :",
children: [
{
id: 11,
name: "src :",
children: [
{ id: 12, name: "v-btn : ts" },
{ id: 13, name: "v-card : ts" },
{ id: 14, name: "v-window : ts" },
],
},
],
},
],
},
{
id: 15,
name: "Downloads :",
children: [
{ id: 16, name: "October : pdf" },
{ id: 17, name: "November : pdf" },
{ id: 18, name: "Tutorial : html" },
],
},
{
id: 19,
name: "Videos :",
children: [
{
id: 20,
name: "Tutorials :",
children: [
{ id: 21, name: "Basic layouts : mp4" },
{ id: 22, name: "Advanced techniques : mp4" },
{ id: 23, name: "All about app : dir" },
],
},
{ id: 24, name: "Intro : mov" },
{ id: 25, name: "Conference introduction : avi" },
],
},
],
}),
};
</script>
カスタム要素化したコンポーネントの呼び出し
カスタム要素化したコンポーネントを外部のファイルから呼び出す際のやり方。
どのサンプルも下記のように簡単に呼び出せます。
ボタンのやつ (simple-vue)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Custom Element By Vue</title>
<script src="dist/bundle.js"></script>
</head>
<body>
<vce-button message="test"></vce-button>
</body>
</html>
テーブルのやつ (Element UI)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Custom Element By Vue + Element UI</title>
<script src="dist/bundle.js"></script>
</head>
<body>
<vce-table
prop1="1"
prop2="example text"
prop3="true"
string-prop="123"
boolean-prop="false"
number-prop="123"
long-prop-name="long name"
>
</vce-table>
</body>
</html>
ツリーのやつ (Vuetify)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Custom Element By Vue + Vuetify</title>
<script src="dist/bundle.js"></script>
</head>
<body>
<vce-tree></vce-tree>
</body>
</html>
vue-cliで生成したやつは、呼び出し元で下記のような記述を足してやる必要がありましたが、これらは必要ありませんでした。
その代わり、カスタム要素化したコンポーネントのサイズが大きくなるので、その点は注意が必要そうです。
<script src="https://unpkg.com/vue"></script>