- 投稿日:2019-04-03T23:48:35+09:00
【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();
関数呼び出しのがトレースされていますね!
使い道が多くて便利そうなメソッドです。はてなブックマーク・Pocketはこちらから
- 投稿日:2019-04-03T23:07:15+09:00
【JavaScript】class構文によるクラス表現【自己学習】
class構文でできること
JavaScriptには他の言語と異なり、クラスという概念が存在しない。
そのため、クラスの概念をfunctionを使って表現していたが、ES6から導入されたclass構文を使うことで、他の言語に慣れた人にもよりわかりやすくクラスの概念を表現できるようになった。
※ES6からクラスの概念が導入されたわけではない点に注意
class構文の書き方
クラスの定義
クラスを定義する方法はクラス宣言とクラス式の2種類がある。
1. クラス宣言
class-1.jsclass User { //コンストラクタの定義 //メソッドの定義 }
class クラス名{}
と記述することでクラス本体を定義する方法。クラス名は必須で頭文字は大文字にする。2. クラス式
class-2.jslet U = class User { //コンストラクタの定義 //メソッドの定義 };
変数or定数 = class クラス名{}
と記述することでクラス本体を定義する方法。クラス名は省略が可能。変数を使って定義した場合は、クラスを再定義することもできる。
コンストラクタの定義
コンストラクタとは、生成されるインスタンスのプロパティを設定する特別なメソッド(後述)を指す。
constructor(引数) {}
と定義することで、インスタンスの生成時に指定した値を引数として受け取ることができる。引数をインスタンスのプロパティの値とする場合には、{}内に
this.プロパティ名 = 引数
とする。constructor-1.jsclass 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.jsclass 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.jsclass 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.jsclass 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」を追加しオーバーライドしている。
- 投稿日:2019-04-03T22:07:30+09:00
Inversion of Markup
Webアプリをモバイルアプリっぽく書ける Yet another なライブライリを作ってみたけど、その特徴をどう呼んだら良いのか分からずモヤモヤしてたので、ここにポエムを書いてみる。
craftkit:
https://craftkit.dev/
https://github.com/craftkitWeb には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
そんな感じでモヤモヤしてるけど、誰か使ってくれたら嬉しいな!
- 投稿日:2019-04-03T21:25:02+09:00
そろそろ 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
opts
をprops
に変更子コンポーネントに値を渡すために、呼び出し側で属性として設定するものですね。
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
,tags
をthis.$
,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 から
observable
はriot
にバンドルされなくなりました。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 が出来たと思っているので、今回のバージョンアップは正当進化だと捉えています。(移行が苦しいことに変わりはないですが?)
- 投稿日:2019-04-03T20:16:55+09:00
Node.jsを用いてSlackのuser idから情報を取得する
Slack APPでユーザーを取得しようとするとユーザーIDが取得される為
APIを用いてユーザーIDからユーザーの詳細情報を取得するrequestモジュール
https://www.npmjs.com/package/request
requestモジュールは HTTP/HTTPS 通信を行うためのクライアント。
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から一気に様々な情報が取れるから便利
- 投稿日:2019-04-03T19:47:46+09:00
5秒当てクイズ
- 投稿日:2019-04-03T18:50:51+09:00
Outgoing WebHooksの代わりをSlack APPで実装する
OutgoingWebHooks と GoogleCloudFunction でSlackチャンネル内の発言に応じた情報の抽出をおこなっていたが
公式は以下のように発表した
これは、従来のカスタム統合に関する情報(チームがSlackと統合するための古い方法)に関する情報を探しているためです。これらの統合はより新しい機能を欠いており、将来廃止される可能性があり、おそらく削除されるでしょう。私達はそれらの使用をお勧めしません。
めっちゃ便利だったのに。。。
親愛なる友を亡くした悲しみは大きいが落ち込んでいる場合じゃない代替えのアプリを作らなければ!!
代わりに、私たちはあなたがそれらの置き換えについて読むことを勧めます - Slackアプリ。スラックのアプリケーションを構築することができるだけで、独自のワークスペースのかのApp Directoryを介して配布され、彼らは最新かつ最高のAPIとUI機能を使用することができます。
公式も自分で作れと言っているようだ
まあ確かに自由度が増したほうが拡張性があるから開発者としては嬉しいかもとりあえず実装してみる
Slack APPの作成
https://api.slack.com/appsにアクセスして、App NameとWorkspaceを入力して Create App で新しいアプリを作成
Bot Usersの作成
作成後、基本情報ページに遷移するので、必要な機能を追加していく
今回はOutgoing WebHooksの代わりなので、Botの設定はいらないが、自動返信なども設定したいのでBotの基本設定も行う
まずはワークスペースを監視するBotの追加をおこなう
必要な情報を記入してBot Userを作成
Event Subscriptionsの有効化
ワークスペース内で起きたイベントに応じて指定したURLにアクセスするように設定する
Request URLに希望のURLを記入
ここで普通にGCFやGASのURLを書き込んでも怒られる
Event Subscriptions の受け取り先になるには、そのサーバーを所有していることを証明する必要があるため、既存のファイルに以下の処理をコピペ。何をやっているかというと、URLを記入欄に入力してきた瞬間に
challenge
っていう値が投げられてくるからchallenge
っていう値を投げ返すだけindex.jsif (payload.type === 'url_verification') { return res.status(200).json({ 'challenge': payload.challenge }); } res.status(200).send('OK');処理を実行するイベントを選択
ご丁寧に説明までつけてくれている
今回はチャンネルにメッセージが投稿されたのをトリガーに処理を実行するので message.channels を選択Bot Eventsも同様に message.channels を選択
ワークスペースへのインストール
設定の OAuth & Permissions の Scopes で、
bot
を追加します。
あとは を押してSlackワークスペースへのインストールが完了
情報の取得
message.channels event を用いて情報を取得すると以下のように飛んでくるので好きに料理する
index.jsif (payload.event && payload.event.type === 'message'){ console.log(payload.event); // payload.eventの中に上記画像の情報が含まれる }文献が英語ばっかりなので拒否反応が出るのはわかるけど
使ってみたらかなり自由度が高くて使いやすいからオススメ
- 投稿日:2019-04-03T18:50:51+09:00
SlackにおけるOutgoing WebHooksの代わりをSlack APPで実装する
OutgoingWebHooks と GoogleCloudFunction でチャンネル内の発言に応じた情報の抽出をおこなっていたが
公式は以下のように発表した
これは、従来のカスタム統合に関する情報(チームがSlackと統合するための古い方法)に関する情報を探しているためです。これらの統合はより新しい機能を欠いており、将来廃止される可能性があり、おそらく削除されるでしょう。私達はそれらの使用をお勧めしません。
めっちゃ便利だったのに。。。
親愛なる友を亡くした悲しみは大きいが落ち込んでいる場合じゃない代替えのアプリを作らなければ!!
代わりに、私たちはあなたがそれらの置き換えについて読むことを勧めます - Slackアプリ。スラックのアプリケーションを構築することができるだけで、独自のワークスペースのかのApp Directoryを介して配布され、彼らは最新かつ最高のAPIとUI機能を使用することができます。
公式も自分で作れと言っているようだ
まあ確かに自由度が増したほうが拡張性があるから開発者としては嬉しいかもとりあえず実装してみる
Slack APPの作成
https://api.slack.com/appsにアクセスして、App NameとWorkspaceを入力して Create App で新しいアプリを作成
Bot Usersの作成
作成後、基本情報ページに遷移するので、必要な機能を追加していく
今回はOutgoing WebHooksの代わりなので、Botの設定はいらないが、自動返信なども設定したいのでBotの基本設定も行う
まずはワークスペースを監視するBotの追加をおこなう
必要な情報を記入してBot Userを作成
Event Subscriptionsの有効化
ワークスペース内で起きたイベントに応じて指定したURLにアクセスするように設定する
Request URLに希望のURLを記入
ここで普通にGCFやGASのURLを書き込んでも怒られる
Event Subscriptions の受け取り先になるには、そのサーバーを所有していることを証明する必要があるため、既存のファイルに以下の処理をコピペ。何をやっているかというと、URLを記入欄に入力してきた瞬間に
challenge
っていう値が投げられてくるからchallenge
っていう値を投げ返すだけindex.jsif (payload.type === 'url_verification') { return res.status(200).json({ 'challenge': payload.challenge }); } res.status(200).send('OK');処理を実行するイベントを選択
ご丁寧に説明までつけてくれている
今回はチャンネルにメッセージが投稿されたのをトリガーに処理を実行するので message.channels を選択Bot Eventsも同様に message.channels を選択
ワークスペースへのインストール
設定の OAuth & Permissions の Scopes で、
bot
を追加します。
あとは を押してSlackワークスペースへのインストールが完了
情報の取得
message.channels event を用いて情報を取得すると以下のように飛んでくるので好きに料理する
index.jsif (payload.event && payload.event.type === 'message'){ console.log(payload.event); // payload.eventの中に上記画像の情報が含まれる }文献が英語ばっかりなので拒否反応が出るのはわかるけど
使ってみたらかなり自由度が高くて使いやすいからオススメ
- 投稿日:2019-04-03T18:38:32+09:00
JSでプリミティブ型を参照渡ししたい場合
プリミティブ型は、そのままでは値渡しになってしまいます。
なので、オブジェクトのプロパティにセットしてあげれば良いです。
- 投稿日:2019-04-03T18:36:38+09:00
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 ではこうならない。謎。
- 投稿日:2019-04-03T17:50:17+09:00
Gmailで「AMP for Email」をテストしてみた
「AMP for Email」とは、AMPのテクノロジーを利用し、動的なコンテンツの配信をメールで可能にするものです。
今回、AMPを使った動的メールをとりあえずテストして、実際にできることと、動作を試しに確認してみました。
「AMP for Email Playground」を使用すると、気軽にAMP Eメールの下書きができたり、ライブプレビューの確認や、送信テストのため、実際に自分のGmailアカウントへ作成したAMP Eメールの送信を行うことができます。
テストするにはGmailの設定変更が必要
Gmailを開き、[設定]→[全般]→[動的メール]に移動して、[動的メールを有効にする]にチェックをいれます。
これにより、テスト目的で動的なEメールを送信できるEメールアドレスをホワイトリストに登録できるダイアログが開きます。
ホワイトリストに入力したアドレスからGmailアカウントに送信されたAMPが適用されたメールは、アカウントがGoogleに登録されていなくても表示されます。
以上の設定で、自分のアカウントに動的メールを送信すれば、期待どおりに機能しているかテストすることができます。
「AMP for Email Playground」から自分のアカウントにAMP Eメールを送信するには、ホワイトリストに「amp@gmail.dev」を登録します。
実際のメールデモ
カルーセル
<amp-carousel>
を使ってみました。
外部から画像を読み込む場合は、Gmailの[設定]→[全般]→[メッセージ内の画像]で、[外部画像を常に表示する]にチェックをいれる必要があります。フォーム
<amp-form>
を使ってみました。
フォームに関しては活用方法を色々考えることができ、夢が拡がりそうですね。
- 投稿日:2019-04-03T17:33:03+09:00
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.仕組み
unifiedとremarkというライブラリを使っています。これらのライブラリを使ってMarkdownをパースしてASTに変換します。詳しい使い方はそれぞれのドキュメントを読んでください。
これらのライブラリを使って生成されるASTはmdastに準拠しています。そしてListItemは
checked
というプロパティの有無でリストかチェックリストかの判別をしているので、ASTを再帰的に走査してListItemのchecked
を(初期値の)false
にすることで変換を実装しています。CLIコマンドとしてそれなりの出来にするためにcommanderを使っています。
感想
誰も使わなさそうですが、JavaScriptからもちゃんと叩けるようにしたいですね。
- 投稿日:2019-04-03T15:45:22+09:00
<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[
と]]>
で囲うということで、あのコメントアウトは何の意味も為していなかった...。結論
いま自分の書いているコードに疑問を持とう。
- 投稿日:2019-04-03T15:28:48+09:00
WEBページの文字をちょっと編集してみるブックマークボタン
まえおき
以前、こんな記事を書きました
HTMLデザイン検収に、WEBページを直接WYSIWYGできる「jQuery Console」を利用する
https://qiita.com/RAWSEQ/items/8cb265318d1339940f66これ、ワンタッチでできたらよくね。とツッコミをいただきました。
「開発ツールで該当の場所探して編集する」という手間は省けたとしても
編集する為にはウィンドウを開かなければいけない。
クールじゃないですよね。そこで ???
本題
EDIT PAGE -「WEBページの見た目(文字)を変えてみることができるブックマークツール」
https://ltside.com/jqc/edit.htmlブックマークをクリックするだけで編集可能になります。
これで、HTMLデザインの幅チェックとか、文言変えた感じのチェックやデモンストレーションがクールにできます。実際はまあ、こんなことしてるだけですが・・
javascriptdocument.querySelectorAll('*').forEach((e)=>{e.setAttribute('contenteditable',true)});開発ツールのスニペット等が使い慣れている方は上記1行実行するだけでテキスト編集可能になります。
その他、相性よさそうなWYSIWYGツールがあれば組み合わせてみたいです。
「もうあるよ」って方も是非教えてください!
- 投稿日:2019-04-03T15:28:48+09:00
WEBページの文字を直接編集してみるブックマークボタン
まえおき
以前、こんな記事を書きました
HTMLデザイン検収に、WEBページを直接WYSIWYGできる「jQuery Console」を利用する
https://qiita.com/RAWSEQ/items/8cb265318d1339940f66これ、ワンタッチでできたらよくね。とツッコミをいただきました。
「開発ツールで該当の場所探して編集する」という手間は省けたとしても
編集する為にはウィンドウを開かなければいけない。
クールじゃないですよね。そこで ???
本題
EDIT PAGE -「WEBページの見た目(文字)を変えてみることができるブックマークツール」
https://ltside.com/jqc/edit.htmlブックマークをクリックするだけで編集可能になります。
これで、HTMLデザインの幅チェックとか、文言変えた感じのチェックやデモンストレーションがクールにできます。実際はまあ、こんなことしてるだけですが・・
javascriptdocument.querySelectorAll('*').forEach((e)=>{e.setAttribute('contenteditable',true)});開発ツールのスニペット等が使い慣れている方は上記1行実行するだけでテキスト編集可能になります。
その他、相性よさそうなWYSIWYGツールがあれば組み合わせてみたいです。
「もうあるよ」って方も是非教えてください!
- 投稿日:2019-04-03T15:13:09+09:00
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となる。
ちなみに矢印をクリックすると中の要素が表示されるがHTMLCollectionの中身は全て表示され、lengthも表示される。NodeListsは中身もなく、lengthも0と表示される。
しかし、HTMLCollectionで取得できた要素ですら、いざ使用しようと思うとなぜかできない。
念の為、console.logでそれぞれのlengthを出してみよう。console.log(menuSeconds.length); console.log(profiles.length);するとどちらの要素も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); }); })();上のようにちゃんと取得される。
また、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() { . . .などとかけば登録できる。
まだまだ未熟者なので至らない箇所があればぜひコメントください。
- 投稿日:2019-04-03T14:13:30+09:00
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イベントを走らせてあげるだけです。
- 投稿日:2019-04-03T14:13:30+09:00
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イベントを走らせてあげるだけです。
- 投稿日:2019-04-03T14:13:30+09:00
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イベントを走らせてあげるだけです。
- 投稿日:2019-04-03T12:43:22+09:00
Nuxt.js に ElementUI を後からいれる
Vue.js製のコンポーネントライブラリ Element UI を Nuxt.js で使いたい場合、
npx create-nuxt-app <project-name>
したときに選択すれば勝手に設定をやってくれる。が、このことに気づかず後から入れることになったので、その手順をメモ。
インストールする
$ npm i element-ui -Sこれで package.json と package-lock.json が更新される。
プラグインを作る
plugins/element-ui.js
を作成する。element-ui.jsimport 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.jsexport.default { ...... css: [ 'element-ui/lib/theme-chalk/index.css' ], plugins: [ '@/plugins/element-ui' ] }あとは ドキュメントを見て使っていきましょう。
【追記】
nuxt.config.js
のbuild
の設定は特にいらないっぽいので削除しました。
- 投稿日:2019-04-03T12:35:37+09:00
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
他にもたくさんあります
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のライブラリはとっても便利でパフォーマンスも良いです。
みんな使いましょう!
- 投稿日:2019-04-03T11:37:38+09:00
Jest で Promise + setTimeout のテストがつまる件
概要
Jest で
Promise
の返り値のテストを書いていたときに
setTimeout
が絡むと非同期のテストがうまく完了しないことに気づきました。例
たとえば文字列を指定回数繰り返す非同期関数のテストを書いてみます (非同期でなくてもいい処理ですが):
repeat.test.tstest('repeat should repeat text given times', async () => { await expect(repeat('go', 5)) .resolves .toBe('gogogogogo'); });
repeat('go', 5)
は'go'
を 5 回繰り返した文字列を返すことをテストする感じにします。
それに沿うように実装してみます:repeat.tsasync 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.tsjest.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.tsasync 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.tstest('repeat should repeat text given times', async () => { useImmediateTimers(); await expect(repeat('go', 5)) .resolves .toBe('gogogogogo'); });これは (この場合) うまくいきます。
しかしながら、実際に経過時刻を見ていたりするケースであったり、
タイマーのキャンセルを行うケースであったりには対応できません。
その場合、内部実装を考えてタイマーを差し替えるというよくない感じになりそうです。
(そもそもタイマーの関連する処理を切り出して Inject できる実装にしておくのも考えられますね)
なにか他に方法があれば教えてください。
- 投稿日:2019-04-03T09:01:58+09:00
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>
- 投稿日:2019-04-03T07:25:24+09:00
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
- 投稿日:2019-04-03T01:39:22+09:00
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/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のページに飛びましょう。NodeCG的にはバージョン8.3以上(2019/04/02現在)を求められますが、特にこだわりがなければLTS版をダウンロードしましょう。
インストーラを実行して、ウィザードの案内に従ってインストールを進めます。
これも特別こだわりがなければ[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の公式ページから、インストーラをダウンロードします。
画像上「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このコマンドを実行すると、実行した場所の直下にフォルダが作成されます。
コマンドプロンプトのカレントディレクトリを適切な作業場所に移動してから、上記コマンドを叩きましょう。コマンドプロンプト上でのフォルダ移動がわからない方は、下記の手順でコマンドプロンプトを起動しましょう。
作業フォルダでコマンドプロンプトを開いたら、コマンドを叩きましょう。
実行コマンドは
git
なので、ここではGitを利用します。
clone
はその名の通り「クローン」を作ります。コピー元のリポジトリを最後のURLで指定します。
これで、https://github.com/nodecg/nodecg.git
上のファイルを、カレントディレクトにコピーすることができます。実行すると、作業フォルダ内に「nodecg」フォルダが作られ、その中に色々なフォルダ/ファイルが入っていると思います。ここでコピーされた資材一式が、NodeCGそのものです。
そのままコマンドプロンプト上で下記コマンドを実行し、「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:90903行目のメッセージで、
http://localhost:9090
でNodeCGが動いてるよ、と言われています。
WebブラウザでこのURLにアクセスしてみましょう。ほぼ何も表示されていませんが、これがNodeCGの操作画面であり「ダッシュボード」と呼ばれる画面です。
今後、インストールされたNodeCGに実際のレイアウトのファイルを導入していくと、このダッシュボードに操作UIが表示されます。NodeCGを終了するには、NodeCGを実行したコマンドプロンプトを閉じるか、コマンドプロンプトでCtrl+Cを押下します。
NodeCGを終了すると、先ほどのURLにアクセスしてもページが表示されなくなります。
まとめ
以上で、NodeCGを導入するための準備+NodeCGの導入・実行までが完了しました。
始めに述べた通り、NodeCGはあくまでもフレームワークであり、枠組みです。レイアウト自体を導入しなければ、まだ何も表示できません。
ということで次回は、Bundle導入編と題して、既存のレイアウトの導入~配信ソフトでの表示までをご紹介していく予定です。
ちなみに
著者は日本国内でもNodeCGを使っている数少ないエンジニアのうちの1人で、RTAコミュニティ内でのオンラインイベントや、名古屋で行われたRTAオフラインイベントのレイアウトを作ったり、あまり前例のないところでは個人で配信しているTwitchのRTA紹介番組でもテロップ等を出す仕組みを提供していたりします。
NodeCGの中身のことはあまり詳しくないですが、NodeCG便利だよ!ってのを少しでも広めれたらなという思いで、色々なところにしゃしゃり出させていただいています。
配信レイアウトを少しリッチにしたいけど、こういうことできる?みたいなことがあれば、ご相談いただければと思います。
宣伝終わり。#2へ続きます。
npmでは「package.json」に、bowerでは「bower.json」にそれぞれ記載されています。 ↩
- 投稿日:2019-04-03T00:41:14+09:00
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」と「本来欲しかった値の型」の共用型になりそうですが、本来欲しかった値の型であるようにすることができるようです。
NullSafeクラス
というわけで、次のようなものを書いてみました。途中経過となる値の取得には
next...
系の関数を、最終的な値の取得にはresult...
系の関数を利用します。また、初めは非同期に関しても扱えるようにしてたのですが、スクレイピングするぐらいでは非同期はもっと外側ですし、自作したクラスも見づらいし、
.next(async o => ...
とするとインデントが崩れて見づらいし、いったん外しました。非同期に対応させるには、サンプルコードをテストコード代わりにしつつ、型のエラーを見つつ、Promiseを適当なところに足していけばできました。NullSafe.tstype 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; } } }