GitHubのissueに特定のラベルが貼られたらSlackに投稿するBOTを作る

特定のリポジトリで、特定のラベルが貼られたらそれを検知してSlackに投稿するBOTをGASで書いた。

github.com

こんな感じにissueに特定のラベルが貼られたら通知を送る f:id:tic40:20181115204625p:plain

やりたかったこと

リポジトリを跨ったアプリケーション構成の場合、Aリポジトリの変更がBリポジトリのアプリケーションに影響を与えることがある。

Aリポジトリに特定のラベルが貼られたら、それをBリポジトリを管理しているチームのSlackチャンネルに通知する、ということをやりたかった。

どうやったか

  • GitHub webhookでラベル変更を検知
  • GASで変更された情報をSlackに投稿すべき内容かどうかチェック
  • GASからSlackにPOSTを投げる

実装手順

GASでスクリプトを書く

claspを使って typescript で記述した。 clasp を使えば、$ yarn clasp push でGASにコードをアップできたり便利。

github.com

  • doPost関数でGitHubのpostを受け取ることができる。
  • 受け取ったらpayloadをチェックする。
  • action プロパティが labeled の場合は新規ラベル追加アクション
  • label プロパティに追加されたラベル情報が入っているので、通知したいラベルかどうかチェック

ソースは以下を参照

https://github.com/tic40/gas-notify-labeled-issue/blob/master/src/index.ts

GASアプリケーションを公開する

GASプロジェクトページからWebアプリケーションとして公開をする。

f:id:tic40:20181115203753p:plain

ここで、現在のWebアプリケーションURLが発行されるのでコピーをしておく

GitHub webhooks を追加する

payload url に先程コピーしたGASアプリケーションのURLをペーストする。 f:id:tic40:20181115204015p:plain

今回hookするイベントは、issues だけで良いので issues のみにチェックを入れる f:id:tic40:20181115204051p:plain

Slack

Incoming WebHooks を通知したいチャンネルに追加する。

WebHook URL が発行されるのでこれもコピーをしておく

f:id:tic40:20181115204307p:plain

GASにスクリプトプロパティの追加

キーや設定情報は、コード内に直接記載せずにGASのスクリプトプロパティに定義している。 SLACK_WEBHOOK_URL などはここに入力している。

const properties = PropertiesService.getScriptProperties()
const SLACK_WEBHOOK_URL: string = properties.getProperty('SLACK_WEBHOOK_URL')
const SLACK_CHANNEL: string = properties.getProperty('SLACK_CHANNEL')
const LABEL_NAME: string = properties.getProperty('LABEL_NAME')
const ICON_EMOJI: string = properties.getProperty('ICON_EMOJI')
const USERNAME: string = properties.getProperty('USERNAME')

GASのプロジェクトページから以下のようにそれぞれ設定を行う

f:id:tic40:20181115204408p:plain

雑感

GAS便利ですね

web componentsについて

web componentsとは何か

Webコンポーネントは、カスタム/再利用可能でカプセル化されたHTMLタグを作成できるWebプラットフォームAPIのセットである。

以下、4つの主要な仕様を持つ。

Custom Elements

Custom Elements

Custom Elementsは、DOM要素を独自に構築する方法を提供する。

Shadow DOM

Shadow DOM

Shadow DOM仕様は、Webコンポーネントカプセル化されたタグを使用する方法を定義する。

ES Modules

ES Modules specification

ESモジュール仕様では、スタンダードベースのモジュール化された方法でJSドキュメントを組み込み、再利用することを定義する。

HTML Template

HTML template element specification

HTML Template仕様では、ページ読み込み時には使用されないが、実行時に後でインスタンス化されるマークアップの断片を宣言する方法を定義する。

demo

custom-btn というカスタム要素を作ってみる

<div>
  <custom-btn></custom-btn>
  <custom-btn>button name</custom-btn>
</div>

<script>
class customBtn extends HTMLElement {
  constructor() {
    super()
    const shadowRoot = this.attachShadow({ mode: 'open' })
    shadowRoot.innerHTML = `
      <button 
        style="background: transparent;">
        <slot>default btn name</slot>
      </button>
    `
  }
}
customElements.define('custom-btn', customBtn)
</script>

demo

See the Pen webcomponents custom btn by tic40 (@ccpzjoh) on CodePen.

ブラウザ対応状況

https://caniuse.com/#search=web%20components

Nuxt.jsにstylelintを導入する

インストールするモジュール

  • stylelint

stylelint.io

  • standard ルールセット

github.com

  • .vueファイル内の<style></style>をパースするために導入する

github.com

インストール

$ yarn add -D stylelint stylelint-config-standard @mapbox/stylelint-processor-arbitrary-tags

設定

プロジェクトディレクトリ配下に、.stylelintrcファイルを作成する

{
  "processors": ["@mapbox/stylelint-processor-arbitrary-tags"],
  "extends": [
    "stylelint-config-standard"
  ]
}

package.jsonにstylelintのscriptを追加する

  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "nuxt generate",
    "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
 +   "lint:css": "stylelint 'src/**/*.vue'",
    "precommit": "npm run lint"
  },

実行

$ yarn lint:css

f:id:tic40:20181015201650p:plain

TIPS

  • scss記法の場合は、オプションで指定できる。 eslint --syntax scss
  • recess-order を入れるとプロパティの宣言順序もチェックしてくれる
  • lintツールに共通して言えることだが、カスタムルールをたくさん追加し出すと後々大変になるので、定番のルールセットを導入してそこからどうしても変えたいものをカスタムルールとして定義する、ぐらいが良い。

Nuxt(Vuex)でAtomic Designを採用するときのメモ

数ヶ月Atomic Designを採用したNuxt(Vuexパターン)アプリケーション開発を行ってみて、思ったことのメモ。

Atomic Designとは

bradfrost.com

概要だけ知りたい人に説明すると、基底となるコンポーネント(原子)を作り、その原子を組み合わせて分子コンポーネントを、さらに分子を組み合わせて有機体コンポーネント、そして最終的にそれらを組み合わせたページを組み上げる、というボトムアップ的アプローチのデザイン手法である。

f:id:tic40:20181007213756p:plain

Nuxt(Vuex)におけるコンポーネント分割方針

Atomic Design手法の肝は、 Atoms/Molecules/Organisms のコンポーネント設計にある。 この設計が抽象的なままだと、アトミックデザインの恩恵は十分に得られない。

コンポーネント設計で、まず最初に頭に入れておきたいのは、

  • Stateless or Stateful
  • 関心の分離

Stateless or Stateful

コンポーネントが状態(data)を持ち、それによって挙動を変える場合はStateful、逆に状態を持たないコンポーネントはStateless。 Statelessにすることで状態による副作用がなくなり、使いやすいコンポーネントになる。 Statelessなコンポーネントでは、watchやmountedといったトリガー、Vuexモジュールへの参照なども副作用を伴うので行わない。

関心の分離

上記を踏まえた上で、Atoms/Molecules/Organismsの設計方針を考える。

Atoms

  • 内部に他のコンポーネントを含まない。ネイティブのHTMLでのみ構成される。
  • 再利用されるコンポーネントである。
  • Statelessである。
  • 単一責任である。
  • Vuexモジュールへの参照は行わない。

Molecules

  • 内部にMolecules以上のコンポーネントを含まない。
  • 再利用されるコンポーネントである。
  • Statelessであることが望ましい。
  • 単一責任である。
  • Vuexモジュールへの参照は行わないのが望ましい。

Organisms

  • 内部にOrganisms以上のコンポーネントを含まない。
  • 再利用性は低くてよい。
  • Statefulである。
  • Vuexモジュールを参照する。

Atomic Design ~堅牢で使いやすいUIを効率良く設計する

Atomic Design ~堅牢で使いやすいUIを効率良く設計する

サーバサイドレンダリング(SSR)について [Nuxt.js]

サーバサイドレンダリング (SSR) とは何か?

Vue SSR ガイド | Vue.js サーバサイドレンダリングガイド

通常では、Vue コンポーネントはブラウザで DOM を生成し操作がされます。しかし、同じ Vue コンポーネントをサーバ上の HTML 文字列に描画し、ブラウザに直接送信し、最終的に静的なマークアップとしてクライアント上の完全なインタラクティブアプリケーションに "ハイドレート (hydrate)" することもできます。

Nuxt.jsを使って実際に比較する

Nuxt.js - Universal Vue.js Applications

Nuxt.jsにはSPAモード(サーバレンダリングなし)とUniversalモード(サーバレンダリングあり)が用意されている。これを使って実際にブラウザにどのようなデータが渡されるのか比較してみる。

環境構築

Installation - Nuxt.js

下のコマンドを入力してポチポチ選択していくだけで構築完了する。

$ npx create-nuxt-app <project-name>

開発サーバを立ち上げる

$ yarn dev

ブラウザで http://localhost:3000 にアクセスし下のような初期ページが表示されれば環境構築完了。

f:id:tic40:20181002204238p:plain

Single Page Application

まずSPAモードで起動*1 ブラウザからソースファイルを表示する。

// ブラウザから表示したソースファイル
<!doctype html>
... 中略
  <body data-n-head="">
    <div id="__nuxt"><style>...</style><script>...</script><div id="nuxt-loading" aria-live="polite" role="status"><div>Loading...</div></div>
... 後略

id="__nuxt"内は、クライアント側でjsを実行しレンダリングするため、実際に表示される要素はソースに含まれていない。

Universal

次にUniversalモード

// ブラウザから表示したソースファイル
<!doctype html>
... 中略
  <body data-n-head="">
    <div data-server-rendered="true" id="__nuxt"><div class="nuxt-progress" style="width:0%;height:2px;background-color:#FFFFFF;opacity:0;"></div><div id="__layout"><div><section class="container"><div><div class="VueToNuxtLogo"><div class="Triangle Triangle--two"></div> <div class="Triangle Triangle--one"></div> <div class="Triangle Triangle--three"></div> <div class="Triangle Triangle--four"></div></div> <h1 class="title">
      nuxt-universal
    </h1> <h2 class="subtitle">
      My spectacular Nuxt.js project
    </h2> <div class="links"><a href="https://nuxtjs.org/" target="_blank" class="button--green">Documentation</a> <a href="https://github.com/nuxt/nuxt.js" target="_blank" class="button--grey">GitHub</a></div></div></section></div></div></div>
... 後略
...

id="__nuxt"内に、既に実際に表示される要素が構築されている。これがサーバレンダリングありとなしの違いになっている。

まとめ

  • SSRにすることで、検索エンジンのクローラが完全に描画されたページを直接解析するため、SEOが向上する。
  • SSRにすることでユーザ体験が向上する。SPAと異なりサーバサイドでマークアップが作られるため、スペックの低いデバイスを使うユーザやネット速度の遅い環境などの場合、ユーザにより速くコンテンツを届けられる。
  • 当然かもしれないがサーバを介する際のみサーバサイドでレンダリングされるので、Nuxt内ルーティングで画面遷移した場合などは、クライアント側でレンダリングを行うことになる。
  • SSRには、Node.jsサーバが必要だったり、サーバサイドの負荷が増える、などといったトレードオフもある。

*1:モードの変更は、nuxt.config.js のmodeプロパティで変更が可能。https://nuxtjs.org/api/configuration-mode/

計算量(O記法)について [30seconds of interviews]

O記法は、コンピュータサイエンスにおいてアルゴリズムの複雑さを表すために使用される。 優秀なアルゴリズムは高速に結果を返し、かつ複雑度は低いと言える。

アルゴリズムは常に同じ結果にはならず、与えられるデータによって異なる結果になる場合がある。同じ要素を扱った場合でも、あるケースでは高速に実行され、他のケースでは低速になるということが起きる。

以下にJavaScriptでの例を示す。例中の基本時間は1msとする。

O(1)

arr[arr.length - 1]

定数時間。1000 elements = 1ms.

arrayの要素数にかかわらず、理論的には同じ毎回時間実行することになる。(データ量に依存せず毎回1msの処理時間)

O(N)

arr.filter(fn)

線形時間。1000 elements = 1000ms.

実行時間はarrayの要素数に比例して線形的に増加する。 これは、関数が結果を返すまでに全てのarrayの要素をループするからである。

O([1, N])

arr.some(fn)

1000 elements = 1ms <= x <= 1000ms.

実行時間は与えられるデータによって異なる。ここでの最良の場合はO(1)であり、最悪の場合はO(N)である。

O(logN)

arr.sort(fn)

対数時間。1000 elements = 3ms.

ブラウザは通常、logNであるsort()メソッドのクイックソートアルゴリズムを実装している。これは大規模な要素に非常に効率的。

O(N2)

for (let i = 0; i < arr.length; i++) {
  for (let j = 0; j < arr.length; j++) {
    // ...
  }
}

二乗時間。1000 elements = 1000000ms.

実行時間は要素の数に応じてその二乗で増えていく。ネストされた二重ループなどはこれにあたる。

O(N!)

const permutations = arr => {
  if (arr.length <= 2) return arr.length === 2 ? [arr, [arr[1], arr[0]]] : arr
  return arr.reduce(
    (acc, item, i) =>
      acc.concat(
        permutations([...arr.slice(0, i), ...arr.slice(i + 1)]).map(val => [
          item,
          ...val
        ])
      ),
    []
  )
}

階乗時間。1000 elements = Infinity (practically) ms.

Nの階乗で実行時間は増加するため、1つの要素を追加するだけでも実行時間の増加に大きく影響する。

tips

  • ネストされたループの実行時間は指数関数的に増加するので、そのようなコードを書く際には計算量に注意。

30secondsofinterviews.org

仮想DOMはなぜライブラリ/FWに使われるのか [30seconds of interviews]

仮想DOMとは

仮想DOM(virtual DOM)は、DOMをJavaScriptオブジェクトの形式で表現したもの。

これらのオブジェクトには、ノード名/属性/子ノードなど、実際に表示されるDOMノードを記述するプロパティがある。

<div class="counter">
  <h1>0</h1>
  <button>-</button>
  <button>+</button>
</div>

上記マークアップの仮想DOMは、次のようになる。

{
  nodeName: "div",
  attributes: { class: "counter" },
  children: [
    {
      nodeName: "h1",
      attributes: {},
      children: [0]
    },
    {
      nodeName: "button",
      attributes: {},
      children: ["-"]
    },
    {
      nodeName: "button",
      attributes: {},
      children: ["+"]
    }
  ]
}

仮想DOMを使う理由

ライブラリやフレームワークは仮想DOMをパフォーマンス向上の手段として使っている。

アプリケーションの状態が変わる時、更新を反映するためにDOMの再構築を行う必要があるが、これにはコストがかかる。

仮想DOMを使うことで、状態が変更されたときに古い仮想DOMと新しい仮想DOMの差分を比較し、その差分だけを実際のDOMに反映させることができる。 そのため、仮想DOMを使うことで一般的にはDOM変更の際のコストを小さくし、その結果表示速度を向上させることができる。

30secondsofinterviews.org