Vuejs自作コンポーネントをライブラリ化
やりたいこと
Vuejsのデータ構造と、カスタムビューコンポーネントを
外部ライブラリ化することで、メインの画面構造を
laravel、nodejs/dot、golang/ginのtemplate経由で
容易に作れるようにしたい
背景
SPA(Vuejs-cli)での画面構成を行うと、微妙な部分での構築がうまくいかない
Vuejs-CDNでは、部分的に適用するのが容易だが、データ構造等はある程度固めておきたい
なので、storeなどのデータ構造や、viewツール等は外部コンポーネントのライブラリとしてSPAで構築するが、画面構造は CDN形式で作りたかった
何に悩んでいたのか?
単純に勉強不足だったのだが、Vue-CDN等でライブラリとして分割している方法を調べていたので、その結果をここに記す
一般的なSPAの構造
こんな感じでmain.js内に Vue の実体をいれるのが多く説明されている
<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>
import Vue from 'vue'
import App from './App'
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
やりたいこと
VueJSのCDNと同じように、コンポーネントをライブラリ化して
以下のように使いたい
<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>
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ではスコープをグローバルにするのは本末転倒かと。
<script type="module">
import PARENT, { MyComponet } from 'parent.esm.js';
console.log(PARENT);
</script>
じゃあ、VueJSなどはどうしているのかと調べたら...
普通に rollup で制御してくれていました
rollup.config.jsで、output.nameを定義すると設定した形式で読み込まれてくれています
<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の名前を定義する
import MyComponent from './mycomponent.vue';
export default MyComponent
export { MyComponent }
と定義したものを読込ませると、以下の実装すると
<html>
..snip..
<script>
new Vue({
el: '#app',
components: { 'mycomponent': MyComponent },
data() {
return { value: "My Component" };
}
});
</script>
</html>
以下のようなエラーが出てしまう
vue.js:634 [Vue warn]: Failed to mount component: template or render function not defined.
found in
---> <Mycomponent>
<Root>
実体を見ると、以下の通りの構造をしているので当たり前である
+ 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>
..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>
終わり..
[参考] サンプルコード
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
{
"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"
}
}
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>
<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>
import MyComponent from './mycomponent.vue';
const PARENT= {
MyComponent
};
export default PARENT
export {
MyComponent
}
<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>