0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[rollup]vuejsのカスタムコンポーネントを外部スクリプトファイルとして提供する

0
Last updated at Posted at 2021-06-07

Vuejs自作コンポーネントをライブラリ化

やりたいこと

Vuejsのデータ構造と、カスタムビューコンポーネントを
外部ライブラリ化することで、メインの画面構造を
laravel、nodejs/dot、golang/ginのtemplate経由で
容易に作れるようにしたい

背景

SPA(Vuejs-cli)での画面構成を行うと、微妙な部分での構築がうまくいかない
Vuejs-CDNでは、部分的に適用するのが容易だが、データ構造等はある程度固めておきたい

なので、storeなどのデータ構造や、viewツール等は外部コンポーネントのライブラリとしてSPAで構築するが、画面構造は CDN形式で作りたかった

何に悩んでいたのか?

単純に勉強不足だったのだが、Vue-CDN等でライブラリとして分割している方法を調べていたので、その結果をここに記す

一般的なSPAの構造

こんな感じでmain.js内に Vue の実体をいれるのが多く説明されている

index.html
<html>
    <head>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>>
    </head>
    <body>
        <div id="app">
        </div>
        <script src="main.js"></script>
    </body>
</html>
main.js
import Vue from 'vue'
import App from './App'

new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
})

やりたいこと

VueJSのCDNと同じように、コンポーネントをライブラリ化して
以下のように使いたい

index.html
<html>
    <head>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script src="app.js"></script>
    </head>
    <body>
        <div id="app">
        </div>
    </body>
    <script>
      new Vue({
        el: '#app',
        components: { App },
      })
    </script>

</html>
app.js
import App from 'app.vue'

export default {
  name: "App",
  template: `<div><a>Hello World!</a></div>`
}

解決させた結果

rollupを使ってのライブラリ化

javascript自体は、全然勉強していなかったので esm等全然しらないというか、ぶっちゃけ今もよくわからないのですが、いろいろと調べた結果、rollup を利用することで実装が出来たので、それらを備忘録の意味も込めてここに記します
※webpack等への連携は、この後頑張りますが、大枠で出来たので記載するかは微妙です

所定の環境を構築

各ソースを展開し、環境構築を行うこと

環境構築
$ mkdir testrollup
$ cd testrollup
$ npm install 
$ npm run build  を実行することで、dist/配下にpackage.jsonで--fileに定義したライブラリファイルが生成される

作り方の説明

実コードは、以降のサンプルコードを参照していただければと思いますので、ポイントのみを抽出して説明します

結局、わかりたかったのは、scriptタグから独自ライブラリを指定させる方法でした
その方法の大事なところは以下のとおりです

pollup.config.js でoutput.name を指定する

esm形式で、名前を指定することはできるが、ESModuleではスコープをグローバルにするのは本末転倒かと。

sample
   <script type="module">
     import PARENT, { MyComponet } from 'parent.esm.js';
     console.log(PARENT);
   </script>

じゃあ、VueJSなどはどうしているのかと調べたら...
普通に rollup で制御してくれていました
rollup.config.jsで、output.nameを定義すると設定した形式で読み込まれてくれています

dist/index.html
<html>
  <head>

    <script src="https://unpkg.com/vuex@3.1.1/dist/vuex.js"></script>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="./parent.min.js"></script> ← ここで取込むと、PARENT としてアクセス可能となる
  </head>
  <body>
   ..snip..
  </body>
  <script>
    console.log(PARENT);
    ..snip..
  </script>
</html>

spa化させる際のexport の構造に注意する

script でhtml上で展開した際のdefaultの名前を定義する

src/entry.js
import MyComponent from './mycomponent.vue';
export default MyComponent
export { MyComponent }

と定義したものを読込ませると、以下の実装すると

dist/index.html
<html>
   ..snip..

  <script>
    new Vue({
      el: '#app',
      components: { 'mycomponent': MyComponent },
      data() {
        return { value: "My Component" };
      }
    });
  </script>
</html>

以下のようなエラーが出てしまう

error
vue.js:634 [Vue warn]: Failed to mount component: template or render function not defined.

found in

---> <Mycomponent>
       <Root>

実体を見ると、以下の通りの構造をしているので当たり前である

scriptで取込んだオブジェクトの実体
+ Object
  MyComponent: {name: "MyComponent", template: "\n  <div>\n    <input\n      type=\"text\"\n      v-mode>\n    <br />\n    <a>{{ message }}</a>\n  </div>\n  ", props: {…}, __file: "src/mycomponent.vue", data: ƒ}
  default: MyComponent: {name: "MyComponent", template: "\n  <div>\n    <input\n      type=\"text\"\n      v-mode>\n    <br />\n    <a>{{ message }}</a>\n  </div>\n  ", props: {…}, __file: "src/mycomponent.vue", data: ƒ}

よって、以下のような実装が必要となる

dist/index.html
<html>
   ..snip..

  <script>
    new Vue({
      el: '#app',
      components: { 'mycomponent': PARENT.default },
        or
      components: { 'mycomponent': PARENT.MyComponent },
       ..snip..
    });
  </script>
</html>

これで何ができるようになるか?

※以下はコンパイル等していないので動くかは不明ですw

実際にやりたいこと
<html>
  <head>

    <script src="https://unpkg.com/vuex@3.1.1/dist/vuex.js"></script>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="./exstore.min.js"></script>  ← exports.nameをExStore
    <script src="./exview.min.js"></script>  ← exports.nameをExView
    <script src="./exsidebar.min.js"></script>  ← exports.nameをExSideBar
  </head>
  <body>
    <div id="app">
      <mybutton eventhandler="onFlipFlot"></mybutton>
      <mycomponent value="hello world"></mycomponent>
      <mysidemenu :top="bTop"></mysidemenu>
    </div>
  </body>
  <script>
    new Vue({
      el: '#app',
      ExStore.Store,   vuejs/store をライブラリ化
      components: {  components をライブラリ化
        'mycomponent': ExView.MyComponent,
        'mybutton': ExView.MyButton,
        'mysidemenu': ExSideBar.SideMenu,
      },
      data() {
        return { value: "My Component", bTop: true };
      },
      methods: {
        onFlipFlot: function() {
          this.bTop = !this.bTop
        }
      }
    });
  </script>
</html>

終わり..

[参考] サンプルコード

tree

testrollup
│  package.json
│  rollup.config.js
│  
├─dist
│      index.html
│      parent.esm.js     [生成ファイル]
│      parent.esm.js.map [生成ファイル]
│      parent.min.js     [生成ファイル]
│      parent.min.js.map [生成ファイル]
│      parent.umd.js     [生成ファイル]
│      parent.umd.js.map [生成ファイル]
│      
└─src
        entry.js
        mycomponent.vue
package.json
{
  "name": "testrollup",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "build": "npm run build:umd & npm run build:es & npm run build:unpkg",
    "build:umd": "rollup --config rollup.config.js --format umd --file dist/parent.umd.js",
    "build:es": "rollup --config rollup.config.js --format es --file dist/parent.esm.js",
    "build:unpkg": "rollup --config rollup.config.js --format iife --file dist/parent.min.js"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@rollup/plugin-buble": "^0.21.3",
    "@rollup/plugin-commonjs": "^19.0.0",
    "@vue/compiler-sfc": "^3.0.11",
    "rollup": "^2.51.0",
    "rollup-plugin-vue": "^6.0.0",
    "vue": "^2.6.13"
  }
}

rollup.config.js
import commonjs from "@rollup/plugin-commonjs";
import vue from "rollup-plugin-vue";
import buble from "@rollup/plugin-buble";

const src_path = './src';

export default {
  input: src_path + '/entry.js',
  output: {
    name: "PARENT",   script でhtml上で展開した際のdefaultの名前を定義する
    exports: "named",
    sourcemap: true,
  },
  plugins: [
    vue({
      css: true, // css を <style> タグとして注入
      compileTemplate: true, // 明示的にテンプレートを描画関数に変換
    }),
    commonjs(),
    buble()
  ],
}

dist/index.html
<html>
  <head>

    <script src="https://unpkg.com/vuex@3.1.1/dist/vuex.js"></script>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="./parent.min.js"></script>
  </head>
  <body>
    <div id="app">
      <mycomponent value="hello world"></mycomponent>
    </div>
  </body>
  <script>
    console.log(PARENT);
    new Vue({
      el: '#app',
      components: { 'mycomponent': PARENT.MyComponent },
      data() {
        return { value: "My Component" };
      }
    });
  </script>
</html>

src/entry.js
import MyComponent from './mycomponent.vue';

const PARENT= {
  MyComponent
};

export default PARENT
export {
  MyComponent
}
src/mycomponent.vue
<script>
export default {
  name: 'MyComponent',
  template : `
  <div>
    <input
      type="text"
      v-model="message"
    />
    <br />
    <a>{{ message }}</a>
  </div>
  `,
  props: {
    value: {
      type: String,
      default: "",
    },
  },
  data: function() {
    return {
      message: this.value
    }
  }
}
</script>

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?