LoginSignup
2
2

More than 3 years have passed since last update.

vue-custom-elementsを利用したVueコンポーネントのカスタム要素化

Posted at

背景

vue-cliを使ったVueコンポーネントのweb-component化は、以下のようなpackage.jsonの書き換えで簡単に実現できたのですが、

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

index.ts
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;
    `,
});
button.vue
<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が効かなくなりスタイルが崩れることぐらいでしょうか。

index.ts
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が効かない
table.vue
<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/

index.ts
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が効かない
tree.vue
<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)

index.html
<!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)

index.html
<!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)

index.html
<!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>
2
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2