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

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

### やりたいこと

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

### 背景

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

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

# 何に悩んでいたのか？

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

### 一般的なSPAの構造

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

```html: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>
```

```javascript:main.js
import Vue from 'vue'
import App from './App'

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

### やりたいこと

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

```html: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>
```

```javascript:app.js
import App from 'app.vue'

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

```

# 解決させた結果

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

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

### 所定の環境を構築

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

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

# 作り方の説明

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

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

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

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

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

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

```html: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の名前を定義する

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

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

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

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

```

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

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

found in

---> <Mycomponent>
       <Root>
```

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

```html: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: ƒ}

```

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

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

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

```

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

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

```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="./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>

```

**終わり..**


# [参考] サンプルコード

```sh: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
```


```javascript: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"
  }
}

```

```javascript: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()
  ],
}

```

```html: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>

```

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

const PARENT= {
  MyComponent
};

export default PARENT
export {
  MyComponent
}
```

```vuejs: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>

```