20190403のJavaScriptに関する記事は26件です。

【JavaScript】スタックトレースをconsole.traceで出力してみた

こんにちは、ブログ「学生ブロックチェーンエンジニアのブログ」を運営しているアカネヤ(@ToshioAkaneya)です。

スタックトレースをconsole.traceで出力してみた

console.traceというメソッドでスタックトレースが表示できるらしいので、使ってみました。
MDN公式ではOutputs a stack trace to the Web Console.と説明されています。
以下のコードを実行してみました。

const func1 = () => {console.trace("TRACE!");}
const func2 = () => {func1();} 
func2();

Screen Shot 2019-04-03 at 23.47.21.png
関数呼び出しのがトレースされていますね!
使い道が多くて便利そうなメソッドです。

はてなブックマーク・Pocketはこちらから

はてなブックマークに追加
Pocketに追加

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】class構文によるクラス表現【自己学習】

class構文でできること

JavaScriptには他の言語と異なり、クラスという概念が存在しない

そのため、クラスの概念をfunctionを使って表現していたが、ES6から導入されたclass構文を使うことで、他の言語に慣れた人にもよりわかりやすくクラスの概念を表現できるようになった。

※ES6からクラスの概念が導入されたわけではない点に注意

class構文の書き方

クラスの定義

クラスを定義する方法はクラス宣言クラス式の2種類がある。

1. クラス宣言
class-1.js
class User {  
  //コンストラクタの定義
  //メソッドの定義
}

class クラス名{}と記述することでクラス本体を定義する方法。クラス名は必須で頭文字は大文字にする。

2. クラス式
class-2.js
let U = class User {  
  //コンストラクタの定義
  //メソッドの定義
};

変数or定数 = class クラス名{}と記述することでクラス本体を定義する方法。クラス名は省略が可能。

変数を使って定義した場合は、クラスを再定義することもできる。

コンストラクタの定義

コンストラクタとは、生成されるインスタンスのプロパティを設定する特別なメソッド(後述)を指す。

constructor(引数) {}と定義することで、インスタンスの生成時に指定した値を引数として受け取ることができる。

引数をインスタンスのプロパティの値とする場合には、{}内にthis.プロパティ名 = 引数とする。

constructor-1.js
class User {  
  constructor(name, address) {  //thisにはインスタンス名が入り、プロパティと値を定義している
    this.name = name;        //user1.name = "山田太郎"
    this.address = address;  //user1.address = "東京都"
  }
}

const user1 = new User("山田太郎","東京都");  //インスタンスを生成
console.log(user1);  // > User {name: 山田太郎, address: 東京都}
console.log(user1.address);  // > 東京都

インスタンス生成時に指定した「山田太郎」と「東京都」の2つの値をconstructorの引数として受け取り、constructor内でnameとaddressの2つのプロパティの値としてそれぞれ定義されている。

また、インスタンスはオブジェクトなので、生成時に定義したインスタンス名user1を参照すると、「User(クラス名) {name: 山田太郎, address: 東京都}」と出力される。

また、オブジェクトと同じ要領でuser1.addressと参照すると、プロパティの値である「東京都」が出力される。

メソッドの定義

メソッドとはオブジェクトのプロパティである関数を指す。class内にメソッド名 {}で定義する。

メソッドには、コンストラクタで定義したインスタンスのプロパティや同じクラス内の他のメソッドを呼び出して、具体的な処理を記述することができる。

method-1.js
class User {  
  constructor(name, address) {  
    this.name = name;        
    this.address = address;  
  }

  greet() {
    console.log(`こんにちは!${this.name}です`); //インスタンスのプロパティの値を利用
  }

  introduce() {
    this.greet();  //同じクラスの他のメソッドを利用
    console.log(`${this.address}に住んでいます`);
  }
}

const user1 = new User("山田太郎","東京都"); 
user1.greet(); // > こんにちは!山田太郎です

user1.introduce();  // > こんにちは!山田太郎です
                    // > 東京都に住んでいます

メソッド内でインスタンスのプロパティの値を呼び出すためには、this.プロパティ名と記述する。また、インスタンス名.メソッド名()でメソッドを呼び出すことができる。

また、メソッド内に同じクラス内の別のメソッドを呼び出す場合は、this.メソッド名と記述する。

クラスの継承

extendsを使ったサブクラスの定義

extendsで既存のクラスの機能を引き継いだサブクラスを定義することができる。

extends-1.js
//method-1.jsで定義したUserクラスを継承したBusinessUserクラスを定義
class BusinessUser extends User { 

}  

const businessuser1 = new BusinessUser("田中一郎", "大阪府"); //サブクラスのインスタンスを生成
businessuser1.introduce();  // > こんにちは!田中一郎です
                            // > 大阪府に住んでいます

上記のコードでは、既存のUserクラスを継承したBusinessUserクラスを定義している。BusinessクラスではUserクラスで定義したコンストラクタやメソッドを呼び出すことができる。

コンストラクタ、メソッドのオーバーライド

継承元のクラス内のメソッドと同名のメソッドをサブクラス内で定義した場合、そのメソッドはオーバーライド(上書き)される。

override-1.js
class BusinessUser extends User { 

  job() {
    console.log("職業は作家です");  //サブクラスで独自に定義したメソッド
  }

  //Userクラスで定義したintroduceメソッドを再定義
  introduce() {
    this.greet();  
    console.log(`${this.address}に住んでいます`);
    this.job();  //メソッドを追加
  }
}  

const businessuser1 = new BusinessUser("田中一郎", "大阪府"); 
businessuser1.introduce();  // > こんにちは!田中一郎です
                            // > 大阪府に住んでいます
                            // > 職業は作家です

上記のコードでは、Userクラスで定義したintroduceメソッドに、jobメソッドを追加した上でオーバーライドさせている。結果、introduceメソッドを呼び出すと、「職業は作家です」が追加されて出力される。

コンストラクタもオーバーライドすることができるが、その場合はコンストラクタ内にsuper(引数)と記述することで継承元のプロパティを参照することもできる。

override-2.js
class BusinessUser extends User { 
  constructor(name, address, job) {
    super(name, address); 
  //super(name, address)でUserクラスの下記のプロパティを参照している
  //this.name = name;
  //this.address = address;

    this.job = job; //新しいプロパティを追加
  }
  usersJob() {
    console.log(`職業は${this.job}です`);
  }

  introduce() {
    this.greet();  
    console.log(`${this.address}に住んでいます`);
    this.usersJob();  
  }
}  

const businessuser1 = new BusinessUser("田中一郎", "大阪府", "銀行員"); 
businessuser1.introduce();  // > こんにちは!田中一郎です
                            // > 大阪府に住んでいます
                            // > 職業は銀行員です

businessuserクラスでは、継承元のプロパティ「name」と「address」をsuperで参照したうえで、独自のプロパティである「job」を追加しオーバーライドしている。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Inversion of Markup

Webアプリをモバイルアプリっぽく書ける Yet another なライブライリを作ってみたけど、その特徴をどう呼んだら良いのか分からずモヤモヤしてたので、ここにポエムを書いてみる。

craftkit:
https://craftkit.dev/
https://github.com/craftkit

Web には2種類あって、それは ”おおよそ静的なサイト” と ”やたら動的なサイト”。

前者の当面のFINALアンサーはWebComponents。

後者は Vue や React が使われているのだけど、どうにも手に馴染まない。
馴染まない原因は多分 Markup に寄せられすぎてるからなのだろうと予てから思っていて、ごにょごにょしてたらある程度まとまった。

つまり

<Hello name="world"></Hello>

を忘れ去って

viewController.append(new Hello({name:"world"}));

と書きたかったんですね。Obj-c 好きだったし。

実現にあたって Shadow Dom をラップして外部から操作できるようにしたのだけど、こうした方法に名前が無かったので一先ず Inversion of Markup と名付けてみた。けど、英語的に意味が伝わるのかどうか分からない。

Shadow Inverse とかの方がより現しているようにも思うけど、これだと何かゲームっぽいよねw

そんな感じでモヤモヤしてるけど、誰か使ってくれたら嬉しいな!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

そろそろ riot@4 への移行をしようじゃないか

Riot.jsのv3からv4へは破壊的な変更が含まれています。
試しに自作のライブラリの一部を v4 へ移行してみたのですが、コードとドキュメントを読みながら試行錯誤の連続だったので、移行手順としてまとめてみました。

前提

執筆時点の最新版 4.0.0-beta5 を対象にしています。

参考にした情報
https://github.com/riot/riot/tree/dev
https://github.com/riot/next

移行手順

拡張子を.riotに変更

まずはじめにファイルの拡張子を変更します。
実は変更しなくても動くんですが、「v4に移行するぞー!」という意思表明で。

<yield /><slot /> に変更

ライブラリを作る時にはよく使いますが、そうでなければ見たことがない人も多いかもです。使ってなければ無視しちゃってください。

- <label><yield /></label>
+ <label><slot /></label>

:scope:host に変更

Scoped CSSの書き方が変わりました。こちらも使っていなければ無視で。

  <style>
-   :scope {
+   :host {
      display: block;
    }
  </style>

export default {} を追加する

テンプレートのHTML部分からアクセスするものはすべて export default {} の中に入れるようになりました。

  <script>
+   export default {
+   }
  </script>

ライフサイクルメソッドを書き換える

引数に props, state とありますが、まずは気にせず変換していきます。

  <script>
    export default {
+     onMounted(props, state) {
+     // ...
+     }
    } 
-   this.on('mount', () => {
-   // ...
-   })  
  </script>

ライフサイクルメソッドは全部で6つあるので、他に使っているものがあれば同様に書き換えてください。
onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted

optsprops に変更

子コンポーネントに値を渡すために、呼び出し側で属性として設定するものですね。
v3 のscript部分では this.opts.xxx とも書けたので、それもあれば変更をお忘れなく。

- <label>{ opts.label }</label>
+ <label>{ props.label }</label>

  <script>
    export default {
      onMounted(props, state) {
-       sampleFunction(opts.checked)
+       sampleFunction(props.checked)
      }
    }
  </script>

opts では値の上書きや追加ができましたが、 props ではどちらもできなくなっています。
状態を保持するには this.state を使いましょう。

- opts.checkd = false
+ this.state.checked = false

タグプロパティを this.state に入れる

タグプロパティは公式の呼び名じゃなかったかも。コードを見て察していただければ?

- <input type="checkbox" checked="{ checked }" />
+ <input type="checkbox" checked="{ state.checked }" />
  <script>
    export default {
+     state {
+       checked: false
+     },
      onMounted(props, state) {
-       this.checked = opts.checked
+       this.state.checked = props.checked
      }
    }
-   this.checked = false
  </script>

ちなみに、テンプレート変数を小さく保つための変数は state ではなく export default {} 配下に移動します。状態を保持するものだけ state に入れるということですね。

  <my-component>
    <!-- state.val ではない -->
    <p>{ val }</p>

    <script>
      export default {
        onBeforeUpdate() {
          // this.state.val ではない
          this.val = some / complex * expression ^ here
        }
      }
    </script>
  </my-component>

プロパティを export default {} に入れる

v4 ではstatic変数になり、インスタンス間で共有されるようになっていました。そのまま v3 の感覚で使っていると事故るので移動させます。
(テンプレートのHTML部分からはアクセスしない変数として使っていたので少し残念です)

  <script>
    export default {
+     defaultChecked: false
    }
-   let defaultChecked = false
  </script>

イベントハンドラを export default {} に入れる

  <input type="checkbox" onclick="{ click }" />
  <script>
    export default {
+     click() {
+     // ...
+     }
    }
-   this.click = () => {
-   // ...
-   }
  </script>

refs, tagsthis.$, this.$$ に変更する

refs で取得していた部分を書き換えるのは大変なので、まずは既存の ref属性を使って選択すればいいと思います。
this.$, this.$$ は内部的に querySelectorAll が実行されています。)

  <h1 ref="header">My todo list</h1>
  <ul>
    <custom-li>Learn Riot.js</custom-li>
    <custom-li>Build something cool</custom-li>
  </ul>

  <script>
    export default {
      onMounted() {
-       const title = this.refs.header
-       const items = this.tags['custom-li']
+       const title = this.$("[ref='header']") // single element
+       const items = this.$$('custom-li') // multiple elements
      }
    }
  </script>

もちろん this.$('h1') でアクセスするように修正して、 ref属性を削除してもOKです。

- <h1 ref="header">My todo list</h1>
+ <h1>My todo list</h1>
  <ul>
    <custom-li>Learn Riot.js</custom-li>
    <custom-li>Build something cool</custom-li>
  </ul>

  <script>
    export default {
      onMounted() {
-       const title = this.$("[ref='header']") // single element
+       const title = this.$("h1") // single element
        const items = this.$$('custom-li') // multiple elements
      }
    }
  </script>

属性にExpressionと文字列を設定している場合は変更する

こちらは4.0.0beta-5までのバグです。
@nibushibu さんが issue をあげてくれたので、次のバージョンでは解消していると思います。

- <div id="{ state.name }-{ state.surname }">
+ <div id="{ state.name + '-' + state.surname }">
    { state.name } - { state.surname }
  </div>

親子間の値の受け渡しは属性を使う

子コンポーネントのタグプロパティとタグメソッドにアクセスできなくなりました。
アクセスできなくなったということは、今までやっていたのが悪手だったということですね。?

  <app>
    <su-checkbox ref="checkbox1">
      Make my profile visible
    </su-checkbox>

-   <p>{ refs.checkbox1.checked ? 'on' : 'off' }</p>
+   <p>{ $("[ref='checkbox1']").getAttribute('checked') ? 'on' : 'off' }</p>
  </app>

- <su-checkbox>
+ <su-checkbox checked="{ state.checked }">
    <!-- ... -->
    <script>
-     this.checked = false
+     export default {
+       state: {
+         checked: false
+       }
+     }
    </script>
  </su-checkbox>

同様に親コンポーネントにアクセスする this.parent も使えなくなっているので変更します。

  <app>
-   <child />
+   <child title="{ title }" />
    <script>
      export default {
        title: 'Sample title'
      }
    </script>
  </app>

  <child>
-   { parent.title }
+   { props.title }
  </child>

observableは明示的に import する

v4 から observableriot にバンドルされなくなりました。v3 の時に riot-route が外れたのと似た感じですね。

  <script>
+   import observable from 'riot-observable'
    export default {
      onMounted(props, state) {
+       this.observable = observable(this)
      },
      onClick() {
-       this.trigger('click')
+       this.observable.trigger('click')
      }
    }
  </script>

おわりに

全体的に色んな書き方があった v3 に対して、規約を明確にしてきた v4 という印象を受けました。
規約が緩かったから Riot.js Style Guide が出来たと思っているので、今回のバージョンアップは正当進化だと捉えています。(移行が苦しいことに変わりはないですが?)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsを用いてSlackのuser idから情報を取得する

Slack APPでユーザーを取得しようとするとユーザーIDが取得される為
APIを用いてユーザーIDからユーザーの詳細情報を取得する

requestモジュール

https://www.npmjs.com/package/request
requestモジュールは HTTP/HTTPS 通信を行うためのクライアント。
image.png

users.infoメソッド

https://api.slack.com/methods/users.info

Slack APIには様々なメソッドが用意されている
今回はアカウントの詳細情報を取得したいので users.infoを使って取得する

Slack APIからの情報取得

簡単に言うと下記のURLにトークンとユーザーIDをつけてアクセスすると詳細情報が返ってくる仕組み

https://slack.com/api/users.info?token=トークン&user=ユーザーID&pretty=1

サンプルコード

Node.jsでAPIを叩くときは以下のように書く
TOKENとUSERIDを書き換える

index.js
// パッケージの読み込み
let request = require('request');

// ヘッダーを定義
var headers = {'Content-type': 'application/json'};

// オプションを定義
var options = {
  url: 'https://slack.com/api/users.info?token=TOKEN&user=USERID&pretty=1',
  method: 'GET',
  headers: headers,
  json: true,
};

// リクエスト送信
request(options, function (error, response, body) {
  console.log(body); // body内に取得したJSONが入っている
})

ユーザーIDを動的にしてAPIを叩く

Slack APPなどから取得したユーザーIDを使って検索する際はES2015(ES6)から追加されたテンプレート構文を用いると便利

JavaScript (ES2015) 文字列中に変数展開できるテンプレート構文のメモ

index.js
// パッケージの読み込み
let request = require('request');

// message.channels eventで取得した場合
if (payload.event && payload.event.type === 'message'){

  // 取得したユーザーIDを代入
  user_id = payload.event.user;

  // ヘッダーを定義
  var headers = {'Content-type': 'application/json'};

  // オプションを定義
  var options = {
    // バックスラッシュ内のテキストに ${変数名} で変数を入れられる
    url: `https://slack.com/api/users.info?token=TOKEN&user=${user_id}&pretty=1`, 
    method: 'GET',
    headers: headers,
    json: true,
  };

  // リクエスト送信
  request(options, function (error, response, body) {
    console.log(body); // body内に取得したJSONが入っている
  })
};

前まではuserを取得したらメールアドレスが返ってきていたような気がするけど
こっちのほうがユーザーIDから一気に様々な情報が取れるから便利

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

5秒当てクイズ

メモ①

function(){
  return;
}

関数を定義してreturnに何も与えないとその関数は何も返さず処理を終了する。

メモ②

element.className

classNameは要素のclass属性の値の取得/設定に使う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Outgoing WebHooksの代わりをSlack APPで実装する

OutgoingWebHooks と GoogleCloudFunction でSlackチャンネル内の発言に応じた情報の抽出をおこなっていたが
公式は以下のように発表した
スクリーンショット 2019-04-03 17.20.47.png

これは、従来のカスタム統合に関する情報(チームがSlackと統合するための古い方法)に関する情報を探しているためです。これらの統合はより新しい機能を欠いており、将来廃止される可能性があり、おそらく削除されるでしょう。私達はそれらの使用をお勧めしません。

めっちゃ便利だったのに。。。
親愛なる友を亡くした悲しみは大きいが落ち込んでいる場合じゃない

代替えのアプリを作らなければ!!

スクリーンショット 2019-04-03 17.27.21.png

代わりに、私たちはあなたがそれらの置き換えについて読むことを勧めます - Slackアプリ。スラックのアプリケーションを構築することができるだけで、独自のワークスペースのかのApp Directoryを介して配布され、彼らは最新かつ最高のAPIとUI機能を使用することができます。

公式も自分で作れと言っているようだ
まあ確かに自由度が増したほうが拡張性があるから開発者としては嬉しいかも

とりあえず実装してみる

Slack APPの作成

https://api.slack.com/appsにアクセスして、App NameとWorkspaceを入力して Create App で新しいアプリを作成
スクリーンショット 2019-04-03 17.37.44.png

Bot Usersの作成

作成後、基本情報ページに遷移するので、必要な機能を追加していく
今回はOutgoing WebHooksの代わりなので、Botの設定はいらないが、自動返信なども設定したいのでBotの基本設定も行う
スクリーンショット 2019-04-03 17.44.21.png
まずはワークスペースを監視するBotの追加をおこなう
スクリーンショット 2019-04-03 17.47.41.png
スクリーンショット 2019-04-03 17.48.37.png
必要な情報を記入してBot Userを作成
スクリーンショット 2019-04-03 17.49.35.png

Event Subscriptionsの有効化

ワークスペース内で起きたイベントに応じて指定したURLにアクセスするように設定する
Request URLに希望のURLを記入
スクリーンショット 2019-04-03 17.57.51.png
ここで普通にGCFやGASのURLを書き込んでも怒られる
Event Subscriptions の受け取り先になるには、そのサーバーを所有していることを証明する必要があるため、既存のファイルに以下の処理をコピペ。

何をやっているかというと、URLを記入欄に入力してきた瞬間にchallengeっていう値が投げられてくるからchallengeっていう値を投げ返すだけ

index.js
if (payload.type === 'url_verification') {
  return res.status(200).json({ 'challenge': payload.challenge });
}
res.status(200).send('OK');

処理を実行するイベントを選択
スクリーンショット 2019-04-03 18.06.26.png
ご丁寧に説明までつけてくれている
今回はチャンネルにメッセージが投稿されたのをトリガーに処理を実行するので message.channels を選択

スクリーンショット 2019-04-03 18.07.36.png

Bot Eventsも同様に message.channels を選択

スクリーンショット 2019-04-03 18.10.11.png
SaveChangeで設定を保存

ワークスペースへのインストール

設定の OAuth & PermissionsScopes で、bot を追加します。
スクリーンショット 2019-04-03 18.27.16.png

あとは スクリーンショット 2019-04-03 18.30.37.png を押してSlackワークスペースへのインストールが完了

情報の取得

message.channels event を用いて情報を取得すると以下のように飛んでくるので好きに料理する
スクリーンショット 2019-04-03 18.40.57.png

index.js
if (payload.event && payload.event.type === 'message'){
  console.log(payload.event); // payload.eventの中に上記画像の情報が含まれる
}

文献が英語ばっかりなので拒否反応が出るのはわかるけど
使ってみたらかなり自由度が高くて使いやすいからオススメ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SlackにおけるOutgoing WebHooksの代わりをSlack APPで実装する

OutgoingWebHooks と GoogleCloudFunction でチャンネル内の発言に応じた情報の抽出をおこなっていたが
公式は以下のように発表した
スクリーンショット 2019-04-03 17.20.47.png

これは、従来のカスタム統合に関する情報(チームがSlackと統合するための古い方法)に関する情報を探しているためです。これらの統合はより新しい機能を欠いており、将来廃止される可能性があり、おそらく削除されるでしょう。私達はそれらの使用をお勧めしません。

めっちゃ便利だったのに。。。
親愛なる友を亡くした悲しみは大きいが落ち込んでいる場合じゃない

代替えのアプリを作らなければ!!

スクリーンショット 2019-04-03 17.27.21.png

代わりに、私たちはあなたがそれらの置き換えについて読むことを勧めます - Slackアプリ。スラックのアプリケーションを構築することができるだけで、独自のワークスペースのかのApp Directoryを介して配布され、彼らは最新かつ最高のAPIとUI機能を使用することができます。

公式も自分で作れと言っているようだ
まあ確かに自由度が増したほうが拡張性があるから開発者としては嬉しいかも

とりあえず実装してみる

Slack APPの作成

https://api.slack.com/appsにアクセスして、App NameとWorkspaceを入力して Create App で新しいアプリを作成
スクリーンショット 2019-04-03 17.37.44.png

Bot Usersの作成

作成後、基本情報ページに遷移するので、必要な機能を追加していく
今回はOutgoing WebHooksの代わりなので、Botの設定はいらないが、自動返信なども設定したいのでBotの基本設定も行う
スクリーンショット 2019-04-03 17.44.21.png
まずはワークスペースを監視するBotの追加をおこなう
スクリーンショット 2019-04-03 17.47.41.png
スクリーンショット 2019-04-03 17.48.37.png
必要な情報を記入してBot Userを作成
スクリーンショット 2019-04-03 17.49.35.png

Event Subscriptionsの有効化

ワークスペース内で起きたイベントに応じて指定したURLにアクセスするように設定する
Request URLに希望のURLを記入
スクリーンショット 2019-04-03 17.57.51.png
ここで普通にGCFやGASのURLを書き込んでも怒られる
Event Subscriptions の受け取り先になるには、そのサーバーを所有していることを証明する必要があるため、既存のファイルに以下の処理をコピペ。

何をやっているかというと、URLを記入欄に入力してきた瞬間にchallengeっていう値が投げられてくるからchallengeっていう値を投げ返すだけ

index.js
if (payload.type === 'url_verification') {
  return res.status(200).json({ 'challenge': payload.challenge });
}
res.status(200).send('OK');

処理を実行するイベントを選択
スクリーンショット 2019-04-03 18.06.26.png
ご丁寧に説明までつけてくれている
今回はチャンネルにメッセージが投稿されたのをトリガーに処理を実行するので message.channels を選択

スクリーンショット 2019-04-03 18.07.36.png

Bot Eventsも同様に message.channels を選択

スクリーンショット 2019-04-03 18.10.11.png
SaveChangeで設定を保存

ワークスペースへのインストール

設定の OAuth & PermissionsScopes で、bot を追加します。
スクリーンショット 2019-04-03 18.27.16.png

あとは スクリーンショット 2019-04-03 18.30.37.png を押してSlackワークスペースへのインストールが完了

情報の取得

message.channels event を用いて情報を取得すると以下のように飛んでくるので好きに料理する
スクリーンショット 2019-04-03 18.40.57.png

index.js
if (payload.event && payload.event.type === 'message'){
  console.log(payload.event); // payload.eventの中に上記画像の情報が含まれる
}

文献が英語ばっかりなので拒否反応が出るのはわかるけど
使ってみたらかなり自由度が高くて使いやすいからオススメ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSでプリミティブ型を参照渡ししたい場合

プリミティブ型は、そのままでは値渡しになってしまいます。
なので、オブジェクトのプロパティにセットしてあげれば良いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Native で、 Release ビルドの時のみ、 resizeMode="center" がバグる

原因不明だが、メモ。

<Image resizeMode="center" source={require('./image.png')} style={styles.image} />

とすると、 本番ビルドの時のみ 画像がおかしくなる。

resizeMode="contain" とすると、ほぼ同じ挙動で治るので、可能であればこちらを使う。

<Image resizeMode="contain" source={require('./image.png')} style={styles.image} />

ちなみに Android ではこうならない。謎。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gmailで「AMP for Email」をテストしてみた

「AMP for Email」とは、AMPのテクノロジーを利用し、動的なコンテンツの配信をメールで可能にするものです。

今回、AMPを使った動的メールをとりあえずテストして、実際にできることと、動作を試しに確認してみました。

AMP for Email Playground」を使用すると、気軽にAMP Eメールの下書きができたり、ライブプレビューの確認や、送信テストのため、実際に自分のGmailアカウントへ作成したAMP Eメールの送信を行うことができます。

テストするにはGmailの設定変更が必要

Gmailを開き、[設定]→[全般]→[動的メール]に移動して、[動的メールを有効にする]にチェックをいれます。

スクリーンショット_2019-04-03_17_06_04.jpg

これにより、テスト目的で動的なEメールを送信できるEメールアドレスをホワイトリストに登録できるダイアログが開きます。

ホワイトリストに入力したアドレスからGmailアカウントに送信されたAMPが適用されたメールは、アカウントがGoogleに登録されていなくても表示されます。

以上の設定で、自分のアカウントに動的メールを送信すれば、期待どおりに機能しているかテストすることができます。

AMP for Email Playground」から自分のアカウントにAMP Eメールを送信するには、ホワイトリストに「amp@gmail.dev」を登録します。

スクリーンショット_2019-04-03_17_06_27.jpg

実際のメールデモ

カルーセル

<amp-carousel>を使ってみました。
外部から画像を読み込む場合は、Gmailの[設定]→[全般]→[メッセージ内の画像]で、[外部画像を常に表示する]にチェックをいれる必要があります。

amp_mail.gif

フォーム

<amp-form>を使ってみました。
フォームに関しては活用方法を色々考えることができ、夢が拡がりそうですね。

amp_mail2.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MarkdownのリストをチェックリストにするCLIコマンドを作った

仕事をしているときにMarkdownの大量のリストをチェックリストに変換したくなったのでそのためのツールを作りました。"list to todo-list"の略で"litto"。

GitHubリポジトリはsosukesuzuki/littoです。

使い方

npmを使ってインストールできます。

$ npm install -g litto

ファイル名を指定して使うことができます。下のようなファイルを./hoge.mdとして作成します。

hoge.md
# ./hoge.md

- item1
- item2

ファイル名を指定してコマンドを叩くと結果が表示されます。

$ litto ./hoge.md
# ./hoge.md

-   [ ] item1
-   [ ] item2

なんかちょっと汚いのでPrettierで整形するためのオプションを用意しました。--formatオプションをつけるとフォーマットした結果が表示されます。

$ litto --write --format ./hoge.md
# ./hoge.md

- [ ] item1
- [ ] item2

--writeコマンドをつけるとファイルを上書きします。

$ litto --write ./hoeg.md
./hoge.md
Done.

仕組み

unifiedremarkというライブラリを使っています。これらのライブラリを使ってMarkdownをパースしてASTに変換します。詳しい使い方はそれぞれのドキュメントを読んでください。

これらのライブラリを使って生成されるASTはmdastに準拠しています。そしてListItemcheckedというプロパティの有無でリストかチェックリストかの判別をしているので、ASTを再帰的に走査してListItemのcheckedを(初期値の)falseにすることで変換を実装しています。

CLIコマンドとしてそれなりの出来にするためにcommanderを使っています。

感想

誰も使わなさそうですが、JavaScriptからもちゃんと叩けるようにしたいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

<script>の中をコメントアウトするやつ、なんなん?

掘り出し物大市。

TL;DR

ガラケーとIE3対応がなければ不要。
まずその前にスクリプトはhtmlから.jsに切り分ける。

経緯

XHTMLの管理画面を触っていて、ふとこんなのを見かける。

<script type="text/javascript">
<!--
// 省略
// --> </script>

この<!--// -->でjavascriptを囲むのが一体何者なのか気になり、調べてみた。

コメントアウトの目的

scriptタグ内が表示(レンダリング)されてしまわないようにする、のは知っていたが、
一体、何に対しての配慮なのかは見当がつかない。

teratailで丁寧に回答されている方が居た。
https://teratail.com/questions/28215#reply-44118
拙い知識で時系列順にまとめると、

  • HTML3.2
    scriptタグの中は将来的にスクリプトに使用し、ブラウザでは非表示にする旨の記述
  • HTML4.01
    scriptタグを理解しないブラウザのための、コメントアウト方法について勧告
    (ここで上記のコメントアウト方法が出たんですね。)
  • XHTML
    scriptタグ内で<![CDATA[]]>で囲う
  • HTML5
    そもそも認識の仕方が違うので、コメントは不要

というところか。

つまり、HTML4.01時代であれば必要だったこともあったが、HTML5で書き始めたら、コメントアウトは不要である。
XHTMLなら<![CDATA[]]>で囲うということで、あのコメントアウトは何の意味も為していなかった...。

結論

いま自分の書いているコードに疑問を持とう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WEBページの文字をちょっと編集してみるブックマークボタン

まえおき

以前、こんな記事を書きました

HTMLデザイン検収に、WEBページを直接WYSIWYGできる「jQuery Console」を利用する
https://qiita.com/RAWSEQ/items/8cb265318d1339940f66

これ、ワンタッチでできたらよくね。

とツッコミをいただきました。

「開発ツールで該当の場所探して編集する」という手間は省けたとしても
編集する為にはウィンドウを開かなければいけない。
クールじゃないですよね。

そこで ???

本題

EDIT PAGE -「WEBページの見た目(文字)を変えてみることができるブックマークツール」
https://ltside.com/jqc/edit.html

ブックマークをクリックするだけで編集可能になります。
これで、HTMLデザインの幅チェックとか、文言変えた感じのチェックやデモンストレーションがクールにできます。

editpageout.gif

実際はまあ、こんなことしてるだけですが・・

javascript
document.querySelectorAll('*').forEach((e)=>{e.setAttribute('contenteditable',true)});

開発ツールのスニペット等が使い慣れている方は上記1行実行するだけでテキスト編集可能になります。

その他、相性よさそうなWYSIWYGツールがあれば組み合わせてみたいです。
「もうあるよ」って方も是非教えてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WEBページの文字を直接編集してみるブックマークボタン

まえおき

以前、こんな記事を書きました

HTMLデザイン検収に、WEBページを直接WYSIWYGできる「jQuery Console」を利用する
https://qiita.com/RAWSEQ/items/8cb265318d1339940f66

これ、ワンタッチでできたらよくね。

とツッコミをいただきました。

「開発ツールで該当の場所探して編集する」という手間は省けたとしても
編集する為にはウィンドウを開かなければいけない。
クールじゃないですよね。

そこで ???

本題

EDIT PAGE -「WEBページの見た目(文字)を変えてみることができるブックマークツール」
https://ltside.com/jqc/edit.html

ブックマークをクリックするだけで編集可能になります。
これで、HTMLデザインの幅チェックとか、文言変えた感じのチェックやデモンストレーションがクールにできます。

editpageout.gif

実際はまあ、こんなことしてるだけですが・・

javascript
document.querySelectorAll('*').forEach((e)=>{e.setAttribute('contenteditable',true)});

開発ツールのスニペット等が使い慣れている方は上記1行実行するだけでテキスト編集可能になります。

その他、相性よさそうなWYSIWYGツールがあれば組み合わせてみたいです。
「もうあるよ」って方も是非教えてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでHTMLCollection/NodeListを使うとき。(JavaScript)

RailsでHTMLCollection・NodeListを使う

今回、getElementByClassNameやquerySelectorAllで要素を取得してイベントを登録する際に引っかかったっ点を備忘録として記述しておく。

まず、要素を取得する時点からミスっていたのでその原因の究明から先に行った。

まず、Railsから要素が取得できていなかったので次のようなコードを書いた。

var menuSeconds = document.getElementsByClassName('menu__second');
var profiles    = document.querySelectorAll('.profile-lists li a');
console.log(menuSeconds);
console.log(profiles);

getElementsByClassNameでは戻り値がHTMLCollection、querySelectorAllでは戻り値はNodoListsとなる。

スクリーンショット 2019-04-03 14.20.32.png

ちなみに矢印をクリックすると中の要素が表示されるがHTMLCollectionの中身は全て表示され、lengthも表示される。NodeListsは中身もなく、lengthも0と表示される。
しかし、HTMLCollectionで取得できた要素ですら、いざ使用しようと思うとなぜかできない。
念の為、console.logでそれぞれのlengthを出してみよう。

console.log(menuSeconds.length);
console.log(profiles.length);

スクリーンショット 2019-04-03 14.28.45.png

するとどちらの要素もlengthが0で何も取得できていないことがわかる。

なぜか。簡潔に言うとこれはJavaSctiptがDOMがrenderされる前に走っているから。つまりHTMLのbodyが読み込まれる前にスクリプトが読み込まれているから。Rails上で先にjsファイルが読み込まれていた。

解決策は
1.HTMLのbodyの最後にscriptタグで囲んで書く。
2.下記のようにDOMが読み込まれるのを待つ記述をする。

(function() {
  'use strict';
   document.addEventListener('DOMContentLoaded', function(e) {
     var menuSeconds = document.getElementsByClassName('menu__second');
     var profiles    = document.querySelectorAll('.profile-lists li a');
     console.log(menuSeconds.length);
     console.log(profiles.length);
   });
 })();

のように書くと、
スクリーンショット 2019-04-03 14.40.12.png

上のようにちゃんと取得される。

また、NodeListsから一つの要素を取り出す際は、profile[0]のように配列と同じでいいが、HTMLCollectionから取り出す際は、menuSeconds.item[0]と、itemをつける必要がある。

イベントリスナーを登録したい

取得した要素にイベントリスナーを登録したいときの処理も備忘録のため。

まず前提としてHTMLCollectiondにはforEachは使えない。よってfor文を使おう。

for (var i = 0; i < menuSeconds.length; i++) {
  menuSeconds.item[i].addEventListener('mouseover', function() {

のように書き始めていた。が、

これだとクロージャが生成されずカウンター変数が保持されないためどこから参照してもlengthの値しかカウントされないらしい。

よって中で即時関数を定義することで解決できる。

for (var i = 0; i < menuSeconds.length; i++) { function(n) {
  menuSeconds.item[n].addEventListener('mouseover', function() {
  .
  .
  .
  }, false);
 })(i);

これでちゃんとイベントが登録されているはず。

NodeListsはforEachが使えるので、

profiles.forEach( pf => {
  pf.addEventListener('mouseover', function() {
  .
  .
  .

などとかけば登録できる。

まだまだ未熟者なので至らない箇所があればぜひコメントください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQueryで作成した文字数カウントで初期表示した際にも文字数カウントさせる

jQueryで作成した文字数をリアルタイムでカウントして表示する機能に、
入力した際だけでなく初期表示の際にもカウントした文字数を表示させてあげる方法についてです。

サンプルコード

sample.html
<p><span class="counter">0</span>文字入力しています</p>
<textarea class="input_message" id="text_input"></textarea>
sample.js
        $(function(){
            $('#text_input').on('keyup change',function(){
            var count = $(this).val().length;
            $('.counter').text(count);
            });
        });

まずは普通の文字数カウントです。
textareaに文字が入力された際に、文字数をカウントしてspanタグ内に表示してくれます。
ただ、初期表示の際にtextareaに既に文字が入力されていたとしても、文字が入力されるまでカウントしてくれません。

上記JSを修正して、初期表示した際にも文字数をカウントするように変えたものが下記になります。

sample.js
        $(function(){
            $('#text_input').on('keyup change',function(){
            var count = $(this).val().length;
            $('.counter').text(count);
            });
            //初期表示した際に対象でkeyupイベントを走らせる
            $('#text_input').trigger("keyup");
        });

keyupイベントを走らせてあげるだけです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQueryで作成した文字数カウントで初期表示した際にも文字数カウントさせる方法

jQueryで作成した文字数をリアルタイムでカウントして表示する機能に、
入力した際だけでなく初期表示の際にもカウントした文字数を表示させてあげる方法についてです。

サンプルコード

sample.html
<p><span class="counter">0</span>文字入力しています</p>
<textarea class="input_message" id="text_input"></textarea>
sample.js
        $(function(){
            $('#text_input').on('keyup change',function(){
            var count = $(this).val().length;
            $('.counter').text(count);
            });
        });

まずは普通の文字数カウントです。
textareaに文字が入力された際に、文字数をカウントしてspanタグ内に表示してくれます。
ただ、初期表示の際にtextareaに既に文字が入力されていたとしても、文字が入力されるまでカウントしてくれません。

上記JSを修正して、初期表示した際にも文字数をカウントするように変えたものが下記になります。

sample.js
        $(function(){
            $('#text_input').on('keyup change',function(){
            var count = $(this).val().length;
            $('.counter').text(count);
            });
            //初期表示した際に対象でkeyupイベントを走らせる
            $('#text_input').trigger("keyup");
        });

keyupイベントを走らせてあげるだけです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQuery製の文字数カウントで、初期表示した際にも文字数カウントさせる方法

jQueryで作成した文字数をリアルタイムでカウントして表示する機能に、
入力した際だけでなく初期表示の際にもカウントした文字数を表示させてあげる方法についてです。

サンプルコード

sample.html
<span class="counter">0</span><p>文字入力しています</p>
<textarea class="input_message" id="text_input"></textarea>
sample.js
        $(function(){
            $('#text_input').on('keyup change',function(){
            var count = $(this).val().length;
            $('.counter').text(count);
            });
        });

まずは普通の文字数カウントです。
textareaに文字が入力された際に、文字数をカウントしてspanタグ内に表示してくれます。
ただ、初期表示の際にtextareaに既に文字が入力されていたとしても、文字が入力されるまでカウントしてくれません。

上記JSを修正して、初期表示した際にも文字数をカウントするように変えたものが下記になります。

sample.js
        $(function(){
            $('#text_input').on('keyup change',function(){
            var count = $(this).val().length;
            $('.counter').text(count);
            });
            //初期表示した際に対象でkeyupイベントを走らせる
            $('#text_input').trigger("keyup");
        });

keyupイベントを走らせてあげるだけです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.js に ElementUI を後からいれる

Vue.js製のコンポーネントライブラリ Element UI を Nuxt.js で使いたい場合、
npx create-nuxt-app <project-name> したときに選択すれば勝手に設定をやってくれる。

Screen Shot 2019-04-03 at 12.30.41.png

が、このことに気づかず後から入れることになったので、その手順をメモ。

インストールする

$ npm i element-ui -S

これで package.json と package-lock.json が更新される。

プラグインを作る

plugins/element-ui.js を作成する。

element-ui.js
import Vue from "vue"
import Element from "element-ui"
import locale from "element-ui/lib/locale/lang/ja"

export default () => {
  Vue.use(Element, { locale })
}

element-ui/lib/locale/lang/ja.js で locale を設定することで、
文言が日本語になる。

element-ui/lib/locale/lang/ja.js
'use strict';

exports.__esModule = true;
exports.default = {
  el: {
    colorpicker: {
      confirm: 'OK',
      clear: 'クリア'
    },
    datepicker: {
      now: '現在',
      today: '今日',
      cancel: 'キャンセル',
      clear: 'クリア',
      confirm: 'OK',
      selectDate: '日付を選択',
      selectTime: '時間を選択',
      startDate: '開始日',
      startTime: '開始時間',
      endDate: '終了日',
      endTime: '終了時間',
      prevYear: '前年',
      nextYear: '翌年',
      prevMonth: '前月',
      nextMonth: '翌月',
      year: '年',
      month1: '1月',
      month2: '2月',
      month3: '3月',
      month4: '4月',
      month5: '5月',
      month6: '6月',
      month7: '7月',
      month8: '8月',
      month9: '9月',
      month10: '10月',
      month11: '11月',
      month12: '12月',
      // week: '週次',
      weeks: {
        sun: '日',
        mon: '月',
        tue: '火',
        wed: '水',
        thu: '木',
        fri: '金',
        sat: '土'
      },
      months: {
        jan: '1月',
        feb: '2月',
        mar: '3月',
        apr: '4月',
        may: '5月',
        jun: '6月',
        jul: '7月',
        aug: '8月',
        sep: '9月',
        oct: '10月',
        nov: '11月',
        dec: '12月'
      }
    },
    select: {
      loading: 'ロード中',
      noMatch: 'データなし',
      noData: 'データなし',
      placeholder: '選択してください'
    },
    cascader: {
      noMatch: 'データなし',
      loading: 'ロード中',
      placeholder: '選択してください'
    },
    pagination: {
      goto: '',
      pagesize: '件/ページ',
      total: '総計 {total} 件',
      pageClassifier: 'ページ目へ'
    },
    messagebox: {
      title: 'メッセージ',
      confirm: 'OK',
      cancel: 'キャンセル',
      error: '正しくない入力'
    },
    upload: {
      deleteTip: 'Delキーを押して削除する',
      delete: '削除する',
      preview: 'プレビュー',
      continue: '続行する'
    },
    table: {
      emptyText: 'データなし',
      confirmFilter: '確認',
      resetFilter: '初期化',
      clearFilter: 'すべて',
      sumText: '合計'
    },
    tree: {
      emptyText: 'データなし'
    },
    transfer: {
      noMatch: 'データなし',
      noData: 'データなし',
      titles: ['リスト 1', 'リスト 2'],
      filterPlaceholder: 'キーワードを入力',
      noCheckedFormat: '総計 {total} 件',
      hasCheckedFormat: '{checked}/{total} を選択した'
    }
  }
};

nuxt.config.js で設定する

css, plugins の登録を行う。

nuxt.config.js
export.default {
  ......

  css: [
    'element-ui/lib/theme-chalk/index.css'
  ],
  plugins: [
    '@/plugins/element-ui'
  ]
}

あとは ドキュメントを見て使っていきましょう。

【追記】

nuxt.config.jsbuild の設定は特にいらないっぽいので削除しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AMPのライブラリをjQueryの代わりに使う

みなさん、jQuery、使っていますか?
先日ウェブサイトの制作をある会社さんに発注したところ、jQuery満載のコードが納品されました。
「うわぁ…」ってなったんですけど、(主に発注ノウハウの)のいい勉強だなあと思ってjQuery排除のリファクタリングをしました。

リファクタリングしている中で、AMPのライブラリ便利だなあって思ったのでここにまとめます。

AMPとは?

Googleが推進している高速なWEBサイト表示技術です。
詳しくはこちらをどうぞ。
https://www.ampproject.org/

AMPのUIライブラリは非AMPページでも使える

AMPに対応するためにはいろいろ規則を守らなければなりませんが、それを守っていられない案件もあるでしょう。
しかし、AMPのための公式JavaScriptライブラリは、非AMP対応ページでももちろん使用可能です

<script async src="https://cdn.ampproject.org/v0.js"></script>

基本はこれだけです。あとはこれに含まれない拡張コンポーネントを使いたい場合にはそのスクリプトを読み込む必要があります。
現時点でも非常に多くのコンポーネントが提供されていて、UIライブラリとしても優秀です。
https://www.ampproject.org/ja/docs/reference/components

このライブラリを使ったからと言って、AMP対応を頑張らないといけないわけではありません
formだって使っていいし、aタグにJavascriptを仕込むのも自由です。

なんだ、ただのGoogle製のUIライブラリじゃないか。

まずは amp-img から

これは有名だと思いますが、遅延読み込みができる画像表示コンポーネントです。
<img>の代わりとして使うだけで、

  • 遅延読み込み
  • レスポンシブ対応などの柔軟なレイアウト

が簡単に実現します。組み込みコンポーネントなので、上記の基本ライブラリをリンクし、

<amp-img alt="A view of the sea"
  src="images/sea.jpg"
  width="900"
  height="675"
  layout="responsive">
</amp-img>

のように記述するだけです。
width, heightを記述し描画速度を高めた上で、レスポンシブ対応までできる方法ってなかなかないのではないでしょうか。
これをjQueryなどで実現しようとすると、jQueryを読み込み、lazyloadを読み込む必要があり遅いです。
もちろん遅延読み込みだけなら他にもjQuery依存のないものがたくさんありますが、AMPはそれだけではないのです。

便利コンポーネントたち

下記のようなコンポーネントが提供されています。いずれも、よく使うけどちょっと厄介なやつらです。

カルーセル

https://www.ampproject.org/ja/docs/reference/components/amp-carousel

アコーディオン

https://www.ampproject.org/ja/docs/reference/components/amp-accordion

lightbox

https://www.ampproject.org/ja/docs/reference/components/amp-image-lightbox

他にもたくさんあります

https://ampbyexample.com/

amp-fx-collection でアニメーション

これを推したくてこの記事を書きました。すごく強力です。

  • 要素が見えるとふわっとでてくるやつ amp-fx="fade-in"
  • パララックス効果 amp-fx="parallax"
  • スクロールに応じて透明度がかわるやつ amp-fx="fade-in-scroll"
  • スクロールすると横からヒュッとでてくるやつ amp-fx="fly-in-right" etc.

などが簡単に実現できます。
こちらに例がたくさんあるのでみてみてください。
https://ampbyexample.com/components/amp-fx-collection/

効果の重ねがけ

すごいのは、上記の効果の重ねがけもできるところです。
効果の個別のライブラリを使っている場合には、競合したりして難しいところがありますが、AMPなら心配いりません。
例えば、

<div amp-fx="fade-in fly-in-right" >
~
</div>

と書けば、ふわっとフェードインしつつ横からヒュッと出てくる、という効果が実現できます。
(ただし、一部、同時に使えない効果があります)

細かい調整もできる

じわ〜っと変わってほしいときか、
最初速くてだんだんゆっくりになるとか、
いろいろ好みがあるとおもいますが、かなり柔軟に指定できます。

  • 効果の持続時間の指定 data-duration="1000ms"
  • 効果のイージングをキーワードで指定 data-easing="ease-in-out" data-easing="linear" など
  • 効果のイージングをベジェ曲線で指定 data-easing="cubic-bezier(1.000, 0.005, 0.725, 0.055)"

自分はベジェ曲線指定が好きなので、できて嬉しいです。

読み込みはasyncで

jQuery系のライブラリでは、asyncで読み込むとうまく動かないことが多い印象です。
ていうかjQuery自体、ふつうは同期で読み込むと思います。
https://jquery.com/
この公式サイトでも下記のように同期で読み込んでますね。(ていうか公式サイトなのにめちゃ古いバージョン使ってて驚く)

<script src="https://code.jquery.com/jquery-1.11.3.js"></script>

もちろん、WEBサイトの描画をブロックしないためには、JavaScriptはなるべく非同期で読み込んだほうがよいです。
その点AMPは、拡張コンポーネントのみならず、基本のコンポーネントも非同期読み込みを前提に作られているので、いつ読み込んでも大丈夫です。描画をブロックしません!

<script async src="https://cdn.ampproject.org/v0.js"></script>
<script async custom-element="amp-fx-collection"
        src="https://cdn.ampproject.org/v0/amp-fx-collection-0.1.js"></script>

結論

というわけで、AMPのライブラリはとっても便利でパフォーマンスも良いです。
みんな使いましょう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jest で Promise + setTimeout のテストがつまる件

概要

Jest で Promise の返り値のテストを書いていたときに
setTimeout が絡むと非同期のテストがうまく完了しないことに気づきました。

たとえば文字列を指定回数繰り返す非同期関数のテストを書いてみます (非同期でなくてもいい処理ですが):

repeat.test.ts
test('repeat should repeat text given times', async () => {
    await expect(repeat('go', 5))
        .resolves
        .toBe('gogogogogo');
});

repeat('go', 5)'go' を 5 回繰り返した文字列を返すことをテストする感じにします。
それに沿うように実装してみます:

repeat.ts
async function repeat(text: string, length: number): Promise<string> {
    // ウェイトをいれる
    await new Promise(
        (resolve, _) => setTimeout(resolve, 10000)
    );

    return Array
        .from({ length }, () => text)
        .join('');
}

無駄に setTimeout を使ってウェイト処理を入れてみました。
10 秒のウェイトをおくようにしています。
実際のケースではもろもろあってウェイトが必要なケースであると想定しています。

このテストの結果はタイムアウトで失敗します:

Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.

Jest の非同期テストのデフォルトのタイムアウトが 5000 ms. だからですね。
しかもこのテストには実際に 10 秒かかってしまいます。
テストケースが増えると面倒なことにもなりそうですね。

useFakeTimers() を使う

Jest にタイマーをフェイクのものに差し替えて
タイミングをコントロールできるようにします:

repeat.test.ts
jest.useFakeTimers();

test('repeat should repeat text given times', async () => {
    const job = repeat('go', 5);
    jest.runAllTimers();
    await expect(job)
        .resolves
        .toBe('gogogogogo');
});

jest.runAllTimers() でフェイクの setTimeout を即時実行することで
非同期テストのタイムアウト問題が解決します。
テストの時間も短縮されました。

Promise + setTimeout

このケースでは文字列の整形結果の取得は同期的に行われていますが、
ウェイトとは別に結果が Promise で非同期に取得されるとしてみます
(実際のケースでは WebAPI 経由での取得が考えられます):

repeat.ts
async function repeat(text: string, length: number): Promise<string> {
    // 結果を WebAPI とかから取得するとする
    const result = await Promise.resolve(
        Array.from({ length }, () => text)
            .join('')
    );
    // ウェイトをいれる
    await new Promise(
        (resolve, _) => setTimeout(resolve, 10000)
    );

    return result;
}

この実装では再び Jest のテストタイムアウトのエラーでテストが失敗してしまいます。
たとえウェイトを 10 秒から 1 msec. に変更したとしてもです。
解決したはずのタイムアウト問題がまた起こってしまいました。

これについては Jest の Issue で報告がされています:
useFakeTimers breaks with native promise implementation
フェイクのタイマーが Promise の実装を壊しているのかもという Issue です。

この問題によって結果の Promise
いつまでたっても resolve されないことが要因になっているようですね。

Workaround

このケースに限って むりやりどうにかする方法を考えてみました:

function useImmediateTimers(global: any = window) {
    global.setTimeout = (
        handler: TimerHandler,
        timeout?: number | undefined,
        ...args: any[]
    ): number => {
        const callableHandler = (typeof handler === 'string')
            ? Function(handler)
            : handler;
        Promise
            .resolve()
            .then(() => callableHandler.apply(args));

        return -1;
    };
}

setTimeout を即時実行するように実装を置き換えてみる方法です。

repeat.test.ts
test('repeat should repeat text given times', async () => {
    useImmediateTimers();
    await expect(repeat('go', 5))
        .resolves
        .toBe('gogogogogo');
});

これは (この場合) うまくいきます。

しかしながら、実際に経過時刻を見ていたりするケースであったり、
タイマーのキャンセルを行うケースであったりには対応できません。
その場合、内部実装を考えてタイマーを差し替えるというよくない感じになりそうです。
(そもそもタイマーの関連する処理を切り出して Inject できる実装にしておくのも考えられますね)
なにか他に方法があれば教えてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

4/3 Javascript コードレシピ集 学習

jsの本を借りたので、毎日継続して定着させていきます。

クリックしたら要素を複製

アンケートのフォームを追加する時に応用出来るはず。

main.js
// 要素を複製 複製したい要素.cloneNode(真偽値) appendChild等と共に使うこと trueを外すと複製されない

// my-clone-boxを取得
const cloneBox = document.getElementById('my-clone-box');

// my-clone-boxをクリックすると発火
cloneBox.addEventListener('click', () =>
{
  // 親要素のcontainerを取得
  const container = document.querySelector('.container');

  // my-clone-boxを複製する
  const clone = cloneBox.cloneNode(true);

  // 複製したmy-clone-boxを、containerの末尾に挿入する
  container.appendChild(clone);
});
js.html
<div class="container">
  <div id="my-clone-box">クリックするとこの要素を複製します</div>
</div>

参考
https://www.imamura.biz/blog/26914

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptでJWTデコード(日本語対応)

Googleのfirebase認証の勉強中にJWTのデコードをJavaScriptで行う方法を調べたのでメモです。
日本語(utf8)が含まれていてもOK。

const parseJwt = (token) => {                                        
  const base64Url = token.split('.')[1];                             
  const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');    
  return JSON.parse(decodeURIComponent(escape(window.atob(base64))));
};                                                                                                                               

参考
How to decode jwt token in javascript - Stack Overflow
window.btoa - Web API | MDN

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

1から学ぶNodeCG#1:NodeCG導入編

はじめに

昨今、HoishinさんのQiitaで解説されていたり、私のTwitterでやかましいくらいに発信しているおかげで、RTA界隈でもNodeCGという固有名詞が割と認識されつつあります。
NodeCGを使ってみたい!レイアウトを作ってみたい!という方を目にする機会も増えてきたので、これを機に導入方法~実際にレイアウトを作ってみるところまでを紹介できればいいなと思っています。

また、Qiitaに書いておいてアレですが、非Node.jsエンジニア向けに記載している箇所を多く含みます。
厳密な説明になっていない箇所が多々あると思いますが、エンジニアでない人にもわかるようにあえて曖昧な記載にしている部分がありますので、ご了承ください。

環境

Windows環境での導入を前提とします。コマンドプロンプトを使用する場面もありますが、MacOSの方は都度読み替えていただければ幸いです。

NodeCGとは

そもそもNodeCGとは何か、というお話しですが、上述したHoishinさんの記事「ライブ配信レイアウトを作るNode.jsのフレームワークで紹介されています。
難しいことはよくわからん!という方は、ひとまず下記の部分だけでも認識しておいてください。

噛み砕くと
Twitch、YouTube Live、ニコニコ生放送などのライブ配信で
動的でリッチな見た目の画面表示を作るための
大枠の仕組みを提供するフルスタックフレームワーク
です。

要するに、ライブ配信上のレイアウトを作成・操作・表示するための枠組みを提供してくれるのがこのNodeCGです。

この導入編では、今ブラウザでこのQiitaを見ているだけの状態から、NodeCGを実行するまでの手順を記載していきます。

NodeCGの公式ページ

何はともあれ、NodeCGの公式ページ(という表記が正確かはわかりませんが今はざっくりこう表記します)を見ましょう。
https://nodecg.com/

1.png

NodeCGの機能説明や仕様が載っています。英語なので抵抗のある方は大変かもしれませんが、日本人で使っている方がまだまだ少ないので、基本的にはこのページと既存のプログラムを見てお勉強しています。

この記事でも、基本は公式ページの記載に則って導入を進めていこうと思います。

Install - NodeCGのインストール

公式ページの手順に従って、NodeCGをインストールします。以下の手順が記載されていますね。

Install Node.js (version 8.3 or greater) & npm (version 2 or greater).

Then, run the following commands from a terminal (command prompt):

... この下にコマンドが羅列されていると思いますが、、、順を追って説明していきます。

Node.jsのインストール

Node.jsとnpmをインストールするように言われているので、インストールします。

Node.jsとは、プログラミング技術の一つで、(これまた語弊を含むかもしれませんが、ざっくり言うと)主にブラウザ上で動作するプログラミング言語「JavaScript」をWebアプリケーション構築に使用できるようにしたものです。

プログラミングにある程度理解のある方はこちらの記事の解説がわかりやすいかと思います。私もこの程度の理解しかしていません。。。

NodeCGもNode.jsのフレームワークであるため、NodeCGを使うにはNode.jsの環境が必須となります。
まずは、記載されたリンクからNode.jsのページに飛びましょう。

2.png

NodeCG的にはバージョン8.3以上(2019/04/02現在)を求められますが、特にこだわりがなければLTS版をダウンロードしましょう。
インストーラを実行して、ウィザードの案内に従ってインストールを進めます。
3.png

これも特別こだわりがなければ[Next]で進めていけばよいでしょう。
保存先ディレクトリを指定する場合は指定してインストールしてください。

インストールウィザードが最後まで完了したら、Node.js及びnpmのインストールは終了です。
コマンドプロンプトで下記のコマンドを実行し、バージョン情報が表示されればインストールは成功です。
※インストールしたバージョンによって表示は異なります。

$ node --version
v8.9.4
$ npm --version
6.9.0

ちなみにNode.jsと一緒にインストールされたnpmとは、Node.jsのパッケージ管理ツールです。
Node.jsは同様にNode.jsで開発された様々な部品(パッケージ)を活用して開発することが多いです。そのパッケージをインストールしたり、アンインストールしたり、記録しておいたりする時に利用するのがnpmです。

小難しいことは一旦置いておいても良いですが、今後の手順の中で
nodeコマンドが出てきたら、Node.jsを使っている とか、npmコマンドが出てきたら、npmでパッケージをいじっているんだなくらいの認識はしておくことをオススメします。

Gitのインストール

NodeCGのインストール手順で、インストールするように記載されていた2つはインストールできました。
が、もう1つ必要なものがあります。公式ページ上の下記コマンドを見てみましょう。

$ git clone https://github.com/nodecg/nodecg.git

gitというコマンドが現れました。何も入れていないWindows環境ではgitコマンドを叩いても何も起きないと思います。

このコマンドは、Gitを利用するためのコマンドです。Gitとは、プログラムを主な対象としたファイルのバージョン管理システムで、「リポジトリ」と呼ばれる保存場所(大きなフォルダみたいなもの)の変更履歴を管理するためのものです。

NodeCGのファイルもGitで管理されており、NodeCGの利用者はNodeCGリポジトリをそのままコピーすることでNodeCGを使うことができます。
したがって、NodeCGの導入のためにはGitを導入する必要があります。

Gitの公式ページから、インストーラをダウンロードします。

4.png

画像上「Download 2.21.0 for Windows」と表示されているリンクから、インストーラがダウンロードできます(バージョンは2019/04/03現在)。
インストーラを実行し、ウィザードに従ってインストールを進めます。細かい設定もできますが、ひとまず何も考えず[Next]で進めていけば良いと思います。気になる方はググってみてください。

ウィザードが完了したら、ここでもコマンドプロンプトでバージョンを確認してみましょう。

$ git --version
git version 2.16.2.windows.1

バージョン情報が表示されていれば、Gitのインストールは完了です。

bowerのインストール

ここから、NodeCGの公式ページに記載されているコマンドを叩いていきます。

何も考えずにコマンドを叩いて進めるだけでも導入できそうですが、せっかくなので1行ずつ内容を見ていきましょう。
まずは1行目のこのコマンドです。

npm install -g bower

コマンドの先頭がnpmなので、npmを実行していることがわかります。npm installで、npmを使ってパッケージをインストールすることを意味します。最後のbowerはインストール対象のパッケージ名で、ここでインストールしたいものがbowerです。
ちなみに、パッケージ名指定の前にある-gはインストール時のオプションで、このコマンドを実行した端末全体で利用できるようにインストールを行うように指定します。ちょっと意味がわからないかもしれませんが、「PCにインストールするためのオプション」とざっくり思ってください。

ということで、このコマンドでbowerというパッケージをインストールしました。bowerもパッケージ管理ツールの一つですが、対象となるものがブラウザ上で表示するような部品であることがnpmとの違いです。

現在bowerの利用は非推奨らしいですが、NodeCGでは未だに使われているので同様に利用する必要があります。そのうちNodeCGでもbowerを使わなくなるかもしれませんが、現状は必須です(maintainerの方もどうにかしたいみたいなことは言ってた気がします)。

上記コマンドを実行したら、bowerのインストールは完了です。

NodeCGをGitリポジトリからコピー

次に実行するのがこのコマンドです。

git clone https://github.com/nodecg/nodecg.git

このコマンドを実行すると、実行した場所の直下にフォルダが作成されます。
コマンドプロンプトのカレントディレクトリを適切な作業場所に移動してから、上記コマンドを叩きましょう。

コマンドプロンプト上でのフォルダ移動がわからない方は、下記の手順でコマンドプロンプトを起動しましょう。

  1. エクスプローラーで作業フォルダを開く。
    5.png

  2. アドレスバーに「cmd」と入力し、Enter。
    6.png

  3. 開いていたフォルダ上でコマンドプロンプトが開きます。
    7.png

作業フォルダでコマンドプロンプトを開いたら、コマンドを叩きましょう。

実行コマンドはgitなので、ここではGitを利用します。
cloneはその名の通り「クローン」を作ります。コピー元のリポジトリを最後のURLで指定します。
これで、https://github.com/nodecg/nodecg.git上のファイルを、カレントディレクトにコピーすることができます。

実行すると、作業フォルダ内に「nodecg」フォルダが作られ、その中に色々なフォルダ/ファイルが入っていると思います。ここでコピーされた資材一式が、NodeCGそのものです。

8.png

そのままコマンドプロンプト上で下記コマンドを実行し、「nodecg」フォルダ内に入りましょう。

cd nodecg

パッケージのインストール(npm / bower)

続いても2コマンド分進みます。以下のコマンドを実行します。

npm install --production
bower install

それぞれnpm、bowerを使って必要なパッケージをインストールします。
ここでインストールするのは、NodeCGの実行に必要なパッケージ群です。

「なぜGitからコピーした上でさらに別のパッケージをインストールするの?」と疑問に思う方もいるかもしれませんが、NodeCGには多くのパッケージが利用されています。それを全てGitリポジトリの管理下に入れるのは、そもそも権利の持ち主が違いますし、プログラムファイル自体も恐らく大きくなってしまうでしょう。

他のパッケージ管理ツールでも同様ですが、基本的に「どのパッケージを使うか」だけをファイル1に記録しておき、実行する側(今回のようにユーザーがいたり、実行環境のサーバーだったり)はそのファイルを見て必要なパッケージを別途インストールします。「どのパッケージ」かを共通認識にするためのツールが、パッケージ管理ツールなのです。

npmコマンドに含まれる--productionは、ざっくり言うと「(製品として)実行に必要なもの」を指定するコマンドです。package.jsonでは、各パッケージが必要になる段階ごとに分けて記載することができ、--productionを設定することで必要なものだけインストールすることができます。

上記2コマンドを実行することで、NodeCGの実行に必要なパッケージがインストールできました。

いざ実行!

ここまででNodeCGの実行に必要なものはすべてインストールできたので、いよいよNodeCGの実行です。

node index.js

nodeコマンドは、Node.jsの実行コマンドです。対象ファイルをNode.jsで実行します。ここでindex.jsはNodeCGの実行プログラムで、このファイルからNodeCGが実行されていきます。

コマンドを叩くと、以下のようなメッセージがコマンドプロンプトに表示されると思います。

[nodecg] No config found, using defaults.
info: [nodecg/lib/server] Starting NodeCG 1.3.2 (Running on Node.js v8.9.4)
info: [nodecg/lib/server] NodeCG running on http://localhost:9090

3行目のメッセージで、http://localhost:9090でNodeCGが動いてるよ、と言われています。
WebブラウザでこのURLにアクセスしてみましょう。

9.png

ほぼ何も表示されていませんが、これがNodeCGの操作画面であり「ダッシュボード」と呼ばれる画面です。
今後、インストールされたNodeCGに実際のレイアウトのファイルを導入していくと、このダッシュボードに操作UIが表示されます。

NodeCGを終了するには、NodeCGを実行したコマンドプロンプトを閉じるか、コマンドプロンプトでCtrl+Cを押下します。

NodeCGを終了すると、先ほどのURLにアクセスしてもページが表示されなくなります。

まとめ

以上で、NodeCGを導入するための準備+NodeCGの導入・実行までが完了しました。

始めに述べた通り、NodeCGはあくまでもフレームワークであり、枠組みです。レイアウト自体を導入しなければ、まだ何も表示できません。

ということで次回は、Bundle導入編と題して、既存のレイアウトの導入~配信ソフトでの表示までをご紹介していく予定です。

ちなみに

著者は日本国内でもNodeCGを使っている数少ないエンジニアのうちの1人で、RTAコミュニティ内でのオンラインイベントや、名古屋で行われたRTAオフラインイベントのレイアウトを作ったり、あまり前例のないところでは個人で配信しているTwitchのRTA紹介番組でもテロップ等を出す仕組みを提供していたりします。

NodeCGの中身のことはあまり詳しくないですが、NodeCG便利だよ!ってのを少しでも広めれたらなという思いで、色々なところにしゃしゃり出させていただいています。

配信レイアウトを少しリッチにしたいけど、こういうことできる?みたいなことがあれば、ご相談いただければと思います。

宣伝終わり。#2へ続きます。


  1. npmでは「package.json」に、bowerでは「bower.json」にそれぞれ記載されています。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScriptにおけるNull条件演算子のとりあえずの代案

とりあえず書いてみたレベルの案です。

現状と理想

Null条件演算子を使いたいケース

例えばQiitaトップページのDOMからコミュニティという文言を取得する」というケース。次のように書くと、どこかでエラーが発生するかもしれない。さらに文言をいくつか抽出するとなった場合、いちいちチェックしたりエラーをキャッチしたりするのが面倒です。結果にはNullか代替値が欲しいです。

const wordTrend = document
  .querySelector('.st-Header_community')
  .childNodes[0]
  .nodeValue;
// 文言「トレンド」を得るか、エラーが発生する

Null条件演算子

C#なんかだと使えるNull条件演算子というものがありますが、これはJavaScriptにはありません

const wordTrend = document?
  .querySelector('.st-Header_community')?
  .childNodes?[0]?
  .nodeValue;
// 文言「トレンド」またはnullが得られる

もしくは次のようにデフォルト値も欲しいかもしれない。

const wordTrend = document?
  .querySelector('.st-Header_community')?
  .childNodes?[0]?
  .nodeValue?
  .default('word-unfetched');
// 文言「トレンド」または文言「word-unfetched」が得られる

代案

サンプル

JavaScriptにはC#の拡張メソッドが書けないので、値をラップしてせめて次のような感じで書けたらわかりやすいでしょうか?

const wordTrend = NullSafe.start(document)
  .next(o => o.querySelector('.st-Header_community'))
  .next(o => o.childNodes)
  .next(o => o[0])
  .next(o => o.nodeValue)
  .resultAlty('word-unfetched');
// 文言「トレンド」または文言「word-unfetched」が得られる

引数oの型が「Null」と「本来欲しかった値の型」の共用型になりそうですが、本来欲しかった値の型であるようにすることができるようです。
o.png

NullSafeクラス

というわけで、次のようなものを書いてみました。途中経過となる値の取得にはnext...系の関数を、最終的な値の取得にはresult...系の関数を利用します。

また、初めは非同期に関しても扱えるようにしてたのですが、スクレイピングするぐらいでは非同期はもっと外側ですし、自作したクラスも見づらいし、.next(async o => ... とするとインデントが崩れて見づらいし、いったん外しました。非同期に対応させるには、サンプルコードをテストコード代わりにしつつ、型のエラーを見つつ、Promiseを適当なところに足していけばできました。

NullSafe.ts
type Getter<TSource, TResult> = (source: TSource) => TResult;

export default class NullSafe<TSource> {
  public static start<TSource>(source: TSource): NullSafe<TSource> {
    return new NullSafe(source);
  }

  private source: TSource | null;

  public constructor(source: TSource | null) {
    this.source = source;
  }

  /** next - 値がNullLike以外なら次の値を取得する。 */
  public next<TResult>(
    getter: Getter<TSource, TResult | null>,
    altResult?: TResult
  ): NullSafe<TResult> {
    // 今の値をチェックする
    if (this.source == null) {
      return new NullSafe<TResult>(null);
    }
    // 次の値を取得する
    let result: TResult | null = getter(this.source);
    // 値をチェックする
    if (result == null) {
      console.trace('値の取得に失敗しました。');
      result = altResult || null;
    }
    // 値をNullSafeにラップする
    return new NullSafe<TResult>(result);
  }
  /** nextAny - 値が何であっても次の値を取得する。 */
  public nextAny<TResult>(
    getter: Getter<TSource | null, TResult>,
    altResult?: TResult
  ): NullSafe<TResult> {
    // 次の値を取得する
    let result: TResult | null = getter(this.source);
    // 値をチェックする
    if (result == null) {
      result = altResult || null;
    }
    // 値をNullSafeにラップする
    return new NullSafe<TResult>(result);
  }

  public result(): TSource | null {
    return this.source;
  }
  public resultAlty<TAlt>(altResult: TAlt): TSource | TAlt {
    if (this.source != null) {
      return this.source;
    } else {
      return altResult;
    }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む