JavaScript
npm
vue.js

[Vue.js] オリジナルコンポーネントライブラリを作って公開しよう!

オリジナルのコンポーネントライブラリを作って公開するまでの手順です。

著者も試行錯誤しながらとりあえず公開できるところまでもっていった段階ですので、至らない点があると思います。
不備を見つけられた方はコメントで教えてくださると幸いです。

完成品はこちらです。

前提

nodeはもちろん、yarnも使いますので入れておいてください。
yarnを使わない方は適宜、npmで読み替えてください。

コンポーネントを作る

まずはディレクリを作って、

$ mkdir my-components-sample
$ cd my-components-sample

yarn initしてpackage.jsonを作ります。
privatefalseにするのがポイントです。trueだと公開できないですからね。
mainは後で書き換えるので適当でいいです。

$ yarn init      
yarn init v1.3.2
question name (my-components-sample): 
question version (1.0.0): 
question description: My Component Sample
question entry point (index.js): 
question repository url: 
question author: 
question license (MIT): 
question private: false
success Saved package.json
✨  Done in 35.73s.

vue.jsを入れます。

$ yarn add vue

適当に2つコンポーネントを作ります。

$ mkdir -p src/components
src/components/Foo.vue
<template>
  <div class="foo">
    Hello Foo {{msg}}
  </div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      required: true,
    }
  }
}
</script>

<style>
.foo {
  color: blue;
}
</style>
src/components/Bar.vue
<template>
  <div class="bar">
    Hello Bar {{msg}}
  </div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      required: true,
    }
  }
}
</script>

<style>
.bar {
  color: red;
}
</style>

webpack設定

webpackを設定していきます。
必要なものをまるっと追加してしましましょう。

package.json
  {
    "name": "my-components-sample",
    "version": "1.0.0",
    "description": "My Component Sample",
    "main": "index.js",
    "license": "MIT",
    "private": false,
    "dependencies": {
      "vue": "^2.5.9"
-   }
+   },
+   "devDependencies": {
+     "autoprefixer": "^7.1.4",
+     "babel-core": "^6.26.0",
+     "babel-loader": "^7.1.2",
+     "babel-preset-env": "^1.6.0",
+     "clean-webpack-plugin": "^0.1.17",
+     "cross-env": "^5.1.1",
+     "css-loader": "^0.28.7",
+     "style-loader": "^0.18.2",
+     "vue-loader": "^12.1.0",
+     "vue-template-compiler": "^2.3.3",
+     "webpack": "^3.6.0",
+     "webpack-merge": "^4.1.0",
+     "webpack-node-externals": "^1.6.0"
+   }
  }
$ yarn install
webpack.config.js
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge')
const CleanWebpackPlugin = require('clean-webpack-plugin');

const baseConfig = {
  output: {
    path: path.resolve(`${__dirname}/dist/`)
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: {
          loader: 'vue-loader',
        }
      }
    ]
  },
  resolve: {
    extensions: ['.vue', '.js'],
    alias: {
      '@src': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src', 'components'),
      'vue$': 'vue/dist/vue.esm.js'
    }
  },
  externals: {
    'vue': 'Vue'
  },
  plugins: [
    new webpack.ProvidePlugin({
      'Vue': 'vue'
    })
  ]
};

let config;
if (process.env.NODE_ENV === 'production') {
  const productionConfig = merge(baseConfig, {
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new webpack.optimize.UglifyJsPlugin({
        minimize: true,
        sourceMap: true,
        mangle: true,
        compress: {
          warnings: false
        }
      }),
      new webpack.LoaderOptionsPlugin({
        minimize: true
      })
    ],
    devtool: '#source-map'
  });
  config = productionConfig // TODO
} else {
  console.error(`\`${process.env.NODE_ENV}\` is not defined.`);
}

module.exports = config;

この状態ではまだoutputもちゃんと指定していないし、エンドポイントもないのでコンパイルできません。
次に進みましょう。

公開する

CDNで公開する

まずはCDNで公開できるようにします。
CDN用のエンドポイントを作成します。

src/plugins.js
module.exports = {
  install: function(Vue, options) {
    Vue.component('foo', require('@components/Foo'));
    Vue.component('bar', require('@components/Bar'));
  }
};

これをエンドポイントとしたwebpackの設定を書いていきます。

webpack.config.js
- config = productionConfig // TODO
+ config = [
+   merge(productionConfig,
+   {
+     entry: path.resolve(__dirname + '/src/plugin.js'),
+     output: {
+       filename: 'my-components-sample.min.js',
+       libraryTarget: 'window',
+       library: 'MyComponentsSample'
+     }
+   })
+ ];

libraryTarget: 'window', library: 'MyComponentsSample'を設定することで、ブラウザでmy-components-sample.min.jsを読み込んだときにwindow.MyComponentsSampleで使えるようになります。

package.jsonに下記を追加します。

package.json
  {
    ...
+   "scripts": {
+     "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
+   },
    ...
  }

すかさずビルド!

$ yarn run build

dist/my-components-sample.min.jsdist/my-components-sample.min.js.mapができていればOKです。

これらのファイルをCDNで公開するわけですが、これはjsDelivrを使うと超がつくほど簡単です。

まずは、packge.jsonに以下を追加します。

packge.json
  {
    ...
    "main": "index.js",
+   "cdn": "dist/my-components-sample.min.js",
    ...
  }

mainは後で使うので書き換えません。

次に、今まで作ったコードをGitHubで公開しましょう。

$ git init
.gitignroe
node_modules/
npm-debug.log
yarn-error.log

distディレクトリを.gitignoreに入れないように注意です。
distは公開しないといけないですからね。

$ git add -A
$ git commit -m "Initial commit"

GitHubのリポジトリを作成する部分は省略します。

$ git remote add origin git@github.com:you/my-components-sample.git
$ git push -u origin master

タグも付けてあげます。

$ git tag v1.0.0
$ git push origin v1.0.0

なんと、これだけでCDNでの公開ができてしまっています。
実際にjsbinで確認してみましょう。

http://jsbin.com/yufohim/edit?html,js,output

やりました。ちゃんと動いています。
(vue.js 2.0.3ではなぜかエラーになって動かなかったので最新バージョンのvue.jsを使っています。)

NPMで公開する

次に、NPMで公開します。
NPM用のエンドポイントを作成します。

src/index.js
import MyFoo from '@components/Foo';
import MyBar from '@components/Bar';

export const VERSION = '1.0.0';

export const Foo = MyFoo;
export const Bar = MyBar;

これをエンドポイントとしたwebpackの設定を書いていきます。

webpack.config.js
  config = [
    merge(productionConfig,
    {
      entry: path.resolve(__dirname + '/src/plugin.js'),
      output: {
        filename: 'my-components-sample.min.js',
        libraryTarget: 'window',
        library: 'MyComponentsSample'
      }
-   })
+   }),
+   merge(productionConfig,
+   {
+     entry: path.resolve(__dirname + '/src/index.js'),
+     output: {
+       filename: 'my-components-sample.js',
+       libraryTarget: 'umd',
+       library: 'MyComponentsSample',
+       umdNamedDefine: true
+     }
+   }),
  ];

yarn run buildを実行してdist/my-components-sample.jsdist/my-components-sample.js.mapができていればOKです。

あとはpackage.jsonを書き換えます。

package.json
- "main": "index.js",
+ "main": "dist/my-components-sample.js",

あとは、npm に push するだけです。

npmのアカウント作成部分は省略します。

npmを汚さないために今回は実際にはpushしません。

$ yarn publish

うまく行っていれば、以下のようにして使うことができるはずです。

$ yarn add my-components-sample
hoge.vue
<script>
import * as My from 'my-components-sample'

export default = {
  components: {
    'my-foo': My.Foo
  }
};
</script>

落ち穂拾い

デプロイを楽にする

デプロイ手順は以下のようになります。

  1. package.jsonindex.jsのバージョン書き換え
  2. yarn run deploy
  3. git add -A && git commit -m 'hogehoge'
  4. git tag v1.x.x && git push origin v1.x.x
  5. yarn publish

毎回これをやるのは面倒なので半自動化しましょう。

deploy.shを作ります(著者はshellが苦手なのでソースが汚いのは勘弁)。

deploy.sh
GEEEN="\e[0;32m"
RED="\e[0;31m"
RESET="\e[0m"
VERSION=$1

# Check version format
if [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ; then
  printf "${GEEEN}Deploy version v$VERSION\n${RESET}"
else
  printf "${RED}VERSION format is 'x.x.x'. ($VERSION)\n${RESET}"
  exit 1
fi

# Check existing tag
git tag | grep v$VERSION > /dev/null
if [ $? -eq 0 ]; then
  printf "${RED}git tag 'v$VERSION' is existing\n${RESET}"
  exit 1
fi

printf "${GEEEN}Rewite src/index.js\n${RESET}"
gsed -r -i "s/VERSION = '[0-9]+\.[0-9]+\.[0-9]+'/VERSION = '$VERSION'/g" src/index.js
git add src/index.js

printf "${GEEEN}Rewite package.json\n${RESET}"
gsed -r -i "s/\"version\": \"[0-9]+\.[0-9]+\.[0-9]+\"/\"version\": \"$VERSION\"/g" package.json
git add package.json

printf "${GEEEN}Build\n${RESET}"
yarn run build
git add dist

printf "${GEEEN}git commit\n${RESET}"
git commit -m "Upgrade to v$VERSION"
git tag v$VERSION

printf "${GEEEN}Please command 'git push origin master && git push origin v$VERSION && yarn publish'\n${RESET}"

実行権限をつけるのを忘れずに。

$ chmod 774 deploy.sh

これを実行するためにはmacの場合gnu-sedが必要です。

$ brew install coreutils
$ brew install gnu-sed

Linuxの場合はgsedsedに置き換えればいいと思います(たぶん)。

package.jsonにdelpoyコマンドを追加してあげましょう。

package.json
  "scripts": {
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
+   "deploy": "./deploy.sh"
  },

あとは、yarn run deploy 1.0.0などと実行すれば、最後のpushの直前までやってくれます。

ドキュメントを書く

せっかく、ライブラリを公開したのですから使ってもらうようにドキュメントを書くといいでしょう。
vue.jsのコンポーネントのドキュメントを書くのにはpropdocを使うのが良さそうです。

propdoc用にコンポーネントを加筆しましょう。

Foo.vue
  <script>
  export default {
+   name: "Foo",
+   introduction: "displaoy message.",
+   description: `
+     This is amazing component.
+   `,
+   token: "<foo msg='ALL OK'></foo>",
    props: {
      msg: {
        type: String,
        required: true,
+       note: 'message'
      }
    }
  };
  </script>

これは以下のように使います。

ComponentDoc.vue
<template>
  <div class="component-doc">
    <article v-for="doc in docs">
      <h1>{{doc.name}}</h1>
      <h2>{{doc.introduction}}</h2>
    </article>
  </div>
</template>

<script>
  import propDoc from 'propdoc'
  import * as My from 'my-components-sample'

export default {
  data: ->
    docs: [
      propDoc.getDoc(My.Foo),
      propDoc.getDoc(My.Bar),
    ]
}
</script>

もっと詳しく知りたい人は公式を確認して見てください。

まとめ

コンポーネントライブラリを作るのは結構かんたんなのでみんさんもオレオレライブラリを作って公開しましょう。
著者もRhoaというライブラリを作っています。楽しいです。

参考