20191125のJavaScriptに関する記事は29件です。

PHP の Error/Exception の分類方法やまとめ方を考えてみたときのメモ。

LogicException と RuntimeException の各子クラスの関連

Logic Runtime 意味
Domain Range 変数が、事前に定義された範囲から逸脱してる。
Out Of Range Out Of Bounds 無効なインデックス、キー。
Invalid Argument Unexpected Value 関数側か、関数の呼び出し側か。
Length Overflow / Underflow すでに長さが変だった / 操作したら長さが変になる。

Domain と Range

単純に、Logic Exception か Runtime Exception か。

Domain は「事前に定義された、過不足なくすべて列挙されたドメインデータの一覧」に含まれない、という例外。

Range は「全て列挙するのは難しいのだけれど、事前に定義された、境界が明確なデータの範囲」から逸脱する、という例外。

正直、この2つについてはもっと根本的な解釈を間違えている可能性があると思ってる。
RangeException の説明に「アンダーフローやオーバーフロー以外の計算エラーが発生した」とあるので、「なにか操作をしようと思ったら変だった(Domain)」「なにか操作をしたら、変になった(Range)」なのかなぁとも思ったりする。

Domain と Range が同じ意味合いで対応する という話は、RangeException の説明に「これ(RangeEaception)は実行時版の DomainException です」とあるので確定。

OutOfRange と OutOfBounds

単純に、Logic Exception か Runtime Exception か。

TypeScript の場合、「コンパイル時に検出」は型定義でできるので、Out of Range は使用する機会がない。

Valid な値が、列挙するのが馬鹿らしいほどの数がある場合に使う例外。
データを列挙できるかできないか?という点で、Domain/Range と OutOfRange/OutOfBounds を使い分ける。(という使い分けでいいのか?)

InvalidArgument と UnexpectedValue

Invalid Argument は呼び出された関数側で throw する。
普通に引数の Validation したときに Invalid なら投げる。

Unexpected Value は関数を呼び出した側で throw する。
関数を呼び出して戻ってきた値が意図した通りになっているかを確認する用途。

例えば https://github.com/sebastianbergmann/diff/blob/master/tests/Utils/UnifiedDiffAssertTrait.php みたいなやつ。

フォームから送られてきた「セレクトボックスやラジオボタンで選んだ値」の改ざんのときは、 DomainException でもいいんじゃね?

そもそも Domain か OutOfBound で迷ってる時点でおかしいんじゃね?

わからん。
いまは、フロントエンドが実装をミスってる可能性もあるので、「一応確認してくれ」という意図で、Domain Exception にしておくのでもいい気がしている。

まぁ、サーバサイドから見たら「外部から来たデータ」であることに変わりはないので、LogicException は不適切なので、Out Of Bounds にしよう派なので、自分で実装するときは Out Of Bounds にするけど。

JavaScript の Error 3種類と、PHP の Exception の対応について

JavaScript には、PHP の例外のような使い方をする(と思われる) Error が3つある。

  • TypeError: 値が期待される型でない場合のエラー
  • RangeError: 値が配列内に存在しない、または値が許容範囲にない場合のエラー
  • ReferenceError: 存在しない変数が参照された場合のエラー

これらは、もしかして PHP の Domain/Range OutOfXxx InvalidXxx のそれぞれに対応するんじゃね?と思ったりした。

ただし、 ReferenceError は Undefined な 変数 にアクセスしようとした場合の話であって、 オブジェクトの Undefined なキー ではない(はず)なので、PHP で言うところの Error レベルの話で、Exception と対比させるのは間違いかもしれない。

PHP Exception JavaScript Error 意味
Domain Range Reference おそらく列挙できるので。ほんとは変数の話だから、 RangeError と見るべきかもしれない。
OutOfRange OutOfBound Range まぁ、普通に範囲エラーだよな。
InvalidArgument UnexpectedValue Type 用途的に考えて同じ。

付録

PHP の Error/Exception の継承関係のクラス図

生成元の Plant UML のソースはこちら

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

勉強のアウトプット ~ブラウザのローカルストレージ基礎編~

はじめに

はじめまして。
某金融系会社でシステムテスト、ホームページのリグレッションテストを主に実施しています。
Qiitaはずっと読んでいるばかりだったのですが、やはりどの記事でも
勉強したらアウトプットが大切!
と仰っていたので、初心者もいいところですが、アウトプットの場として使わせていただければと思います。
投稿するのは恐らくJavaScriptUiPathについてが多くなると思います。
よろしくおねがいします。
それでは。
Twitterは自分からは載せません……ゲームの話ばかりで恥ずかしい

そもそもなんでローカルストレージを学んだのか?

知るきっかけは、システムテストをしている際に、

ローカルストレージを利用して、ユーザ設定値を保存している

という機能でした。
なんとなく聞いたことあるけれども、そういえばきちんと理解したことはないな、と思い調べてみました。
また、CookieSessionについても、きちんと理解したことはなかったので
これを期にちゃんと勉強しよう、と思った次第です。

ローカルストレージとは

ローカルストレージとは、その名の通りではありますが、各ブラウザのローカルで保存するストレージのことです。

調べた際にそのように出てきて、そりゃそうだろうよ、となったので、もう少し調べて、自分なりに解釈してみました。

Qiitaでは、 @pipiox さんが書かれた
CookieとWebStorageとSessionについてのまとめ
を参考にさせて頂きました。有り難うございます。

  1. 情報をユーザのブラウザに保存しておく機能
  2. "key":"value"という、keyに対応したvalueが1セットで保存される
  3. どちらも文字列でのみ保存される
  4. 自分で明示的に削除しない限り、半永久的に保存される
  5. サーバとのやり取りは発生しない

というのがローカルストレージの特徴でしょうか。

実際に、どうやってローカルストレージにアクセスするのか

JavaScriptの簡単なコードによってアクセスが可能です。

ローカルストレージにセットするとき

localStorageプロパティのsetItem()メソッドで値をセットします。

SetItemTest.js
localStorage.setItem(key, value);

ローカルストレージから値を取得するとき

localStorageプロパティのgetItem()メソッドで値を取得します。
戻りはvalueに保存している文字列です。

GetItemTest.js
localStorage.getItem(key);

ローカルストレージの一つの値を削除するとき

localStorageプロパティのremoveItem()メソッドで値を削除できます。
keyに対応するもののみを削除します。

RemoveItemTest.js
localStorage.removeItem(key);

ローカルストレージすべてを削除するとき

localStorageプロパティのclear()メソッドで値を削除できます。
removeItem()と違い、こちらはすべて削除します。

ClearTest.js
localStorage.clear();

以上になります。
valueにセットする際、オブジェクトを渡すこともできるそうなのですが、
私はまだどうすればよいかわからず…。

今回ローカルストレージで簡単なToDoリストを作ってみようと思ったのもありますので、
不要かな、と……。

ゆくゆくはしっかり調べて使えるようになりたいな、とは思います!
お読み頂き、ありがとうございました!

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

画面遷移をしないでURLを変更する方法!

実現したかったこと

画面遷移をしないでリアクティブにUIを変更したかった!

histroy.pushState() で出来た!

history.pushState(null, null, '?search=東京都');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プライベートリポジトリをパスワードで限定公開するアプリを作った

GitHubのプライベートリポジトリを不特定多数に公開したい時ってありません? 私はあります、書類選考など。
不特定多数だから一人一人招待は無理だし、パスワード認証程度でいいんだけどなあと思いながら、
GitHubの設定でどうにかできないかと調べてみたんですが、ダメですね、なかったです。かなしいです。

クソみたいなリポジトリなんてどうせ誰も見ないんだからパブリックにしちゃえば?と思われるかもしれません。
パブリックはなんか嫌だし、私が作ったものはクソじゃないし、百歩譲ってクソだったとしても自分のクソを全世界に公開する趣味はないです

なので作りました。他になさそうだったので。

http://35.233.244.144/
(クリックしても飛べない場合URLをコピーしてみてください)

GitHubのリポジトリはこちら

ss.png

MineSweeper3Dのみパスワード公開してます。「unity」

これなに?

Webアプリケーションです。
使用技術は
フロント:React
サーバー:Golang echo
DB:Mongo
これはGCEのインスタンスの中のDockerで動いてます。
Webサービスではないので自分のリポジトリに適用させる場合はサーバー立ててCloneして云々が必要です。

なぜWebサービスにしなかったのか

これはGitHubのアクセストークンを使用し、プライベートリポジトリを取得してきています。
悪意のある運営者であれば、サービスを使用した人のプライベートリポジトリを取得できます。
それって気持ち悪くない? 私だったら利用しません。なのでWebサービスにはしませんでした。

どのように動いているのか

GitHub APIではアクセストークンを使用してリポジトリのファイルを取得できます。トークンは生成する必要があります。
https://api.github.com/repos/ユーザー名/リポジトリ/contents?access_token=トークン
みたいな。最初のディレクトリしか出ないので全て取得するにはディレクトリかを判別して再帰的に繰り返す必要があります。

サーバーを立てる前にGolangで対象のリポジトリをクローリングするところから始まります。
./repo -repo yourrepo -expire 2020-02-02 -password yourpassword
こんなコマンドをサーバー内で実行します。
これはプロジェクトのファイル一覧を再帰的に取得してDBに入れています。

あとはサーバーを立ててパスワード認証をして、認証に成功したらファイルを表示させてます。
ファイルは逐一GitHub APIを介してます。
用途としては、プライベートリポジトリの完全なミラーが目的ではなく、パスワード認証して限定公開が主目的だからです。
コマンド実行時のディレクトリ構造を保存し、そのパスをもとにファイルにアクセスしています。

全体的に難しいことはしてません。

ss2.png

まとめ

本当に技術的に気になりましたらソースを見てください。
私のようにプライベートリポジトリを限定公開したいという人がいましたら、これを使うなり、使わなくともこれが参考になればと思い、記事をしたためました。
調べても同様のものが見つからなくて絶望した過去の私のような人間が一人でも救われるのであれば幸いです。

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

DOMの超基礎知識

DOM

  • Document Object Model
  • 階層構造を持った文書のモデル
  • WEBページを動的に書き換えるために必要
  • DOMが作成されるタイミングはWEBページが読み込まれた後
  • JavaScriptのプログラムではDOMにアクセスし、情報を読み取り、書き換えることで、HTMLにDOMの内容が反映される

DOM作成後にJsプログラムを実行するには

次のような記述でDOM作成後に処理を実行できる

dom.js
$(function() {
  //処理
});

Qiitaで良い記事書ける人ってすごい。精進します。

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

React Hooksの魅力

ようこそ、React Hooks Advent Calendar 2019へ!まずは1日目ということで、React Hooksの魅力について語ってみます。

React Hooksの魅力

関数コンポーネントでも広がる機能性

もともとReactのコンポーネントはクラスコンポーネントしかなく、そしてrenderだけで済むものが関数コンポーネントで書けるようになりました。ただ、関数コンポーネントを拡張していって、stateなどが必要になると、改めてクラスコンポーネントに書き換える必要が出てしまいました1

React Hooksでは、関数コンポーネントのまま、stateや更新時のフックを実現できるようになります。

カスタムフックによるモジュール化

クラスコンポーネントでは、クラス内部にcomponentDidMountcomponentDidUnmountといったコールバックは1つしか作れなかったので、前準備と後始末を分けて書くしかない、ということがありました。

React Hooksでは、コールバック類をいくつでも書けます。さらに、複数のHooksをまとめて呼び出すカスタムフックを作ることで、準備と後片付けをまとめて再利用することも可能としています。

粒度の細かな制御

クラスコンポーネントには、全体をメモ化して、propsstateが変化しない限り再描画を抑える機能があります。また、componentDidUpdateは更新のたびに実行されます。

React Hooksの場合、useMemoを使えば「特定の変数群が変わらない限り」という条件でメモ化ができますし、useEffectでも同様に「特定の変数群の変化」をきっかけとしてコールバックを実行できます。


  1. React 16.12時点でも、componentDidCatchgetSnapshotBeforeUpdateといった一部のコールバックは関数コンポーネントで再現できません。 

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

Node.js でつくる WASMコンパイラー - Extra1:WASIを使ってWASMを動かす

はじめに

Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニコンパイラー」「ミニインタープリター」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成する小さなコンパイラーに取り組んでいます。

今回の目的

前回で目標としていたNode.js-WASMコンパイラーの最低限の実装が終わりました。今回は生成したWASMをいろいろな環境で動かすべく WASI(WebAssembly System Interface)に対応させたいと思います。

WASI とは

WASIはWebAssemblyをウェブ以外の場所(ブラウザやNode.js以外の環境)で動かせる様にする取り組みです。

WASMのコードを、いろいろなプラアットフォーム上で動かせる様にシステムコールに相当するAPIを標準化する試みです。様々なランタイムが実装されていて、CDNのエッジサーバーや組み込みデバイスで動かす試みもあります。

  • wasmtime ... Rustで作られた、リファレンス的なランタイム環境
  • lucet ... Fastlyが取り組んでいる、CDNエッジ上でWASMを実行することを目指したランタイム
  • WebAssembly Micro Runtime ... 組み込みでも使えることを目指した、軽量ランタイム(JITコンパイラーなし、インタープリターのみ)

WASIを使えば、将来的にCDN上や組み込みデバイス上でWASMを実行できるはずです。ワクワクしますね。

WASIで使える関数

WASIではOSを抽象化して、ファイルやネットワークなどの入出力にアクセスできるようになります。実際にサポートされるAPIはこちらにまとめられています。

これを見ると、「System Interface」と言うだけあってC言語のprintf()やputs()などは存在せず、よりプリミティブな関数がサポートされています。今回のミニWASMコンパイラーの組み込み関数putn()/puts()を実現するために、次の関数を利用することにします。

Hello, WASI

WASI の実行環境

今回はWASIの実行にwasmtimeを使います。ビルドにはRustとcargoが必要です。

wasmtimeのビルド
$ git clone --recurse-submodules https://github.com/bytecodealliance/wasmtime.git
$ cd wasmtime
$ cargo build --release
$ ./target/release/wasmtime --version
0.7.0

文字列の出力

さっそくWASIを使った文字列出力にチャンレンジしてみます。次のWATファイルを用意しました。

hello_wasi.wat
(module
    ;; -- WASIの fd_write()をインポイートするため宣言 -- 
    ;;    fd_write(File Descriptor, *iovs, iovs_len, nwritten)
    ;;      -> Returns number of bytes written
    (import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))

    (memory 1)
    (export "memory" (memory 0))
    (data (i32.const 16) "Hello WASI\n") ;; 'Hello WASI\n' をメモリ上に確保 (offset 16 bytes, length 11 bytes)

    ;; -- メイン関数は _start() としてエクスポート --
    (func $main (export "_start")
        ;; iov (バッファーのアドレスと、長さのセット)をメモリ上に用意
        (i32.store (i32.const 0) (i32.const 16))  ;; バアッファーの先頭アドレス(=offset)
        (i32.store (i32.const 4) (i32.const 11))  ;; バッファーの長さ

        (call $fd_write
            (i32.const 1) ;; ファイルでスクリプタ - 1:stdout
            (i32.const 0) ;; iovのセットへのアドレス
            (i32.const 1) ;; iovのセットの長さ - [buffer, length]のセットの数
            (i32.const 8) ;; *nwritten - 出力されたバイト数を受け取るポインター
        )
        drop ;; 戻り値として出力されたバイト数が帰ってきているので、それを破棄
    )
)
  • fd_write()関数をインポート
  • エントリーポイントとなるメイン関数を _start() という名前でエクスポート
  • 出力する文字列をメモリ上に確保
  • 「文字列バッファーの先頭アドレスと、その長さ」のセットをメモリ上に確保 ... iov
  • 先のiovのアドレスと、そのセット数を指定して、fd_write()を呼び出す

実行結果はこちら

$ wasmtime hello_wasi.wat
Hello WASI

無事出力されました。

WASI対応コンパイラー

これまで作った Node.js-WASMコンパイラー mininode_wasm_08.jsを、WASI向けに改造します。

WASI関数のインポート

いままでは呼び出し側(Node.js)でputn(), puts()の実体を用意したものをWASM内部でインポートしていました。その代わりにWASIのランタイムからfd_write()をインポートします。

// ---- compile simplified tree into WAT ---
function compile(tree, gctx, lctx) {
  // ... 省略 ...

  let block = '(module' + LF();
  // -- builtin func (imports) --
  block = block + TAB() + ';; ---- builtin func imports ---' + LF();

  // --- normal WASM ---
  //block = block + TAB() + '(func $putn (import "imports" "imported_putn") (param i32))' + LF();
  //block = block + TAB() + '(func $puts (import "imports" "imported_puts") (param i32))' + LF();

  // --- WASI ---
  block = block + TAB() + '(import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))' + LF();

  // ... 省略 ...
}

メイン関数の宣言

メイン関数のエクスポート宣言部分も_start()に変更します。

// ---- compile simplified tree into WAT ---
function compile(tree, gctx, lctx) {
  // ... 省略 ...

  block = block + TAB() + ';; ---- export main function  ---' + LF();

  // --- normal WASM ---
  //block = block + TAB() + '(export "exported_main" (func $main))' + LF();

  // --- WASI ---
  block = block + TAB() + '(export "_start" (func $main))' + LF();

  // ... 省略 ...


}

整数の出力 putn()

インポートしたfd_write()を内部で呼び出して、符号付32ビット整数を表示するputn()関数を作ります。あらかじめWATで記述した別ファイルを用意した関数を用意しておき、コンパイラでWATを生成する際に連結する方式ににします。

putn() は内部で次の処理を行います。

  • (1) 整数値を文字列で表現した時の桁数を算出する
    • この時、マイナス値の場合はマイナス記号分もカウントする
  • (2) 整数値がマイナスの場合は絶対値をとる
  • (3) 一桁ずつ取り出し、1文字のASCIIキャラクターに変換、メモリー上に格納する
    • 例) 1 --> 49 (0x31)
  • (4) 最後に改行文字(`\n')を入れる、マイナス値だったら先頭にマイナス記号を格納する
  • (5) fd_write()を呼び出すためのパラメーターをメモリー領域に準備する
    • パラメーターは、あらかじめメモリー上の決まった位置にダミーの値で確保
    • 毎回値を書き換えて使う
  • (6) fd_write()を呼びだす

これを全てWATで手書きするのはなかなか骨が折れます。そこで、一部をJSで記述して、前回までのコンパイラーを使ってWATを生成することにしました。直接メモリーをいじったり関数を呼び出すところはコンパイラーでサポートしていないので、手書きすることになります。

整数を文字列に変換する部分を _convI32ToString() として抜き出してJSで実装します。担当する処理は上記(1)~(3)の範囲です。

_convI32ToString()
function _convI32ToString(n) {
  let restValue = n;
  let isMinus = 0;
  let dummy;
  if (_isMinus(n)) {
    restValue = -n;
    isMinus = 1;
    dummy = _storeChar(0, 45); // minus mark '-'
  }

  let len = _calcLength(restValue);
  let idx = len - 1;
  let digitChar = 0;
  while (idx >= 0) {
    digitChar = _getOneDigit(restValue);
    _storeChar(idx + isMinus, digitChar);

    restValue = _div10(restValue);
    idx = idx - 1;
  }

  return len + isMinus;
}

実際にはさらに次の内部関数を呼び処理を行っています。
- _calcLength() ... 整数が文字列にした場合に何桁になるかを算出
- _getOneDigit() ... 整数の一の位をASCIIコードに変換
- _div10() ... 整数を1/10にする
- _isMinus() ... 整数がマイナス値かどうかを判定
- _storeChar() ... 1文字分をメモリーに格納するダミー
- 実際にはWASMのメモリーに値を格納する処理に後で置き換える

// calc char length of int32
//  NOT support minus
function _calcLength(n) {
  let restValue = n;
  let len = 1;
  while (restValue >= 10) {
    restValue = restValue / 10;
    len = len + 1;
  }

  return len;
}

// get 1 digit char code
function _getOneDigit(n) {
  const r = n % 10;
  const c = 48 + r;  // '0' + r
  return c;
}

// div 10
function _div10(n) {
  const d = n / 10; // calc as int
  return d;
}

// --- for node direct ---
//let _strBuf = '....................';

function _storeChar(idx, charCode) {
  puts(' _storeChar() called. idx, charCode bellow');
  putn(idx);
  putn(charCode);

  /* --- for Node.js direct --- */
  //let ch = String.fromCharCode(charCode);
  //_strBuf = _strBuf.slice(0, idx) + ch + _strBuf.slice(idx + 1);

  return 0;
}

function _isMinus(n) {
  if (n < 0) {
    return 1;
  }

  return 0;
}

前回のWASMコンパイラー mininode_wasm_08.jsでコンパイルした結果の抜粋はこちらです。実際にはこれを手で修正して利用しています。

watの抜粋
 (func $_calcLength (param $n i32) (result i32)
    (local $restValue i32)
    (local $len i32)
    get_local $n
    set_local $restValue

    i32.const 1
    set_local $len

    loop ;; --begin of while loop--
      get_local $restValue
      i32.const 10
      i32.ge_s
      if
        get_local $restValue
        i32.const 10
        i32.div_s

        set_local $restValue

        get_local $len
        i32.const 1
        i32.add

        set_local $len

        br 1 ;; --jump to head of while loop--
      end ;; end of if-then
    end ;; --end of while loop--
    get_local $len
    return

    i32.const 88
    return
  )

  (func $_div10 (param $n i32) (result i32)
    (local $d i32)
    get_local $n
    i32.const 10
    i32.div_s

    set_local $d

    get_local $d
    return

    i32.const 88
    return
  )

  (func $_convI32ToString (param $n i32) (result i32)
    (local $restValue i32)
    (local $isMinus i32)
    (local $dummy i32)
    (local $len i32)
    (local $idx i32)
    (local $digitChar i32)
    get_local $n
    set_local $restValue

    i32.const 0
    set_local $isMinus

    get_local $n
    call $_isMinus

    if
      i32.const 0
      get_local $n
      i32.sub

      set_local $restValue

      i32.const 1
      set_local $isMinus

      i32.const 0
      i32.const 45
      call $_storeChar

      set_local $dummy

    end

    get_local $restValue
    call $_calcLength

    set_local $len

    get_local $len
    i32.const 1
    i32.sub

    set_local $idx

    i32.const 0
    set_local $digitChar

    loop ;; --begin of while loop--
      get_local $idx
      i32.const 0
      i32.ge_s
      if
        get_local $restValue
        call $_getOneDigit

        set_local $digitChar

        get_local $idx
        get_local $isMinus
        i32.add

        get_local $digitChar
        call $_storeChar

        get_local $restValue
        call $_div10

        set_local $restValue

        get_local $idx
        i32.const 1
        i32.sub

        set_local $idx

        br 1 ;; --jump to head of while loop--
      end ;; end of if-then
    end ;; --end of while loop--
    get_local $len
    get_local $isMinus
    i32.add

    return

  )

この生成した関数を使って、putn()を実現します。

putn()
  (func $putn(param $n i32)
    (local $strLen i32)
    get_local $n
    call $_convI32ToString ;; ret=Lenght
    set_local $strLen

    ;; write tail LF
    i32.const 12 ;; head of string buffer
    get_local $strLen
    i32.add
    i32.const 10 ;; LF
    i32.store8 

    ;; +1 length for tail LF
    get_local $strLen
    i32.const 1
    i32.add
    set_local $strLen

    ;; iov.iov_base 
    i32.const 4
    i32.const 12
    i32.store

    ;; iov.iov_len
    i32.const 8
    get_local $strLen
    i32.store

    ;; $fd_write
    i32.const 1 ;; file_descriptor - 1 for stdout
    i32.const 4 ;; *iovs - The pointer to the iov array, which is stored at memory location 0
    i32.const 1 ;; iovs_len - We're printing 1 string stored in an iov - so one.
    i32.const 0 ;; nwritten - A place in memory to store the number of bytes writen
    call $fd_write

    drop ;; Discard the number of bytes written from the top the stack
  )

文字列の出力 puts()

同様に、固定文字列を表示するputs()関数も作ります。puts() は内部で次の処理を行います。

  • (1) 出力する文字列のアドレスを受け取る
  • (2) 別のメモリー領域に文字列をコピーする
  • (3) 最後に改行文字(`\n')を入れる
  • (4) fd_write()を呼び出すためのパラメーターをメモリー領域に準備する
    • パラメーターは、あらかじめメモリー上の決まった位置にダミーの値で確保
    • 毎回値を書き換えて使う
  • (5) fd_write()を呼びだす

これを全てWATで手書きするのはなかなか骨が折れます。そこで、一部をJSで記述して、前回までのコンパイラーを使ってWATを生成することにしました。直接メモリーをいじったり関数を呼び出すところはコンパイラーでサポートしていないので、手書きすることになります。

今回のputs()の例では「(2)別のメモリー領域に文字列をコピーする」部分をJSファイルで書いてからコンパイラーで生成したものを参考にし、残りは手書きで作りました。

puts()
  (func $puts (param $n i32)
    (local $srcIdx i32)
    (local $destIdx i32)
    (local $len i32)
    (local $c i32)
    get_local $n
    set_local $srcIdx

    i32.const 0
    set_local $destIdx

    i32.const 0
    set_local $len

    get_local $srcIdx
    call $_loadChar

    set_local $c

    loop ;; --begin of while loop--
      get_local $c      
      if
        get_local $destIdx
        get_local $c
        call $_storeChar

        get_local $len
        i32.const 1
        i32.add

        set_local $len

        get_local $srcIdx
        i32.const 1
        i32.add

        set_local $srcIdx

        get_local $destIdx
        i32.const 1
        i32.add

        set_local $destIdx

        get_local $srcIdx
        call $_loadChar
        set_local $c

        ;; check lenght 255
        get_local $destIdx
        i32.const 255
        i32.lt_s
        br_if 1

        ;; br 1 ;; --jump to head of while loop--
      end ;; end of if-then
    end ;; --end of while loop--

    ;;get_local $len
    ;;call $putn

    ;; tail LF
    get_local $destIdx
    i32.const 10 ;; LF
    call $_storeChar

    get_local $len
    i32.const 1
    i32.add
    set_local $len

    ;; iov.iov_base 
    i32.const 4
    i32.const 12
    i32.store

    ;; iov.iov_len
    i32.const 8
    get_local $len
    i32.store


    ;; $fd_write
    i32.const 1 ;; file_descriptor - 1 for stdout
    i32.const 4 ;; *iovs - The pointer to the iov array, which is stored at memory location 0
    i32.const 1 ;; iovs_len - We're printing 1 string stored in an iov - so one.
    i32.const 0 ;; nwritten - A place in memory to store the number of bytes writen
    call $fd_write

    drop ;; Discard the number of bytes written from the top the stack 
  )

WASI対応コンパイラーの拡張

テンプレートの用意

用意したビルトイン関数putn(), puts()はこちらの別ファイルに保存しておき、コンパイラーで読み込んで使います。

テンプレート読み込みモジュール

今回のミニNode.js-WASMコンパイラーでは、最初に作っていた「ミニインタープリター」で動かす、という縛りを設けています。ミニインタープリターではファイルの読み書きを直接はサポートしておらず、外部モジュールとして準備しています。なので今回のテンプレートファイルも外部モジュールを用意してそちらで読み込みます。

// -------------------------
// module_wasibuiltin.js - WASM builtin for WASI
// - puts()
// - putn()
// -------------------------

'use strict'

const fs = require('fs');
const println = require('./module_println.js');
const abort = require('./module_abort.js');
const printWarn = require('./module_printwarn.js');

const builtinTamplateFile = 'wasi_builtin_template.watx';

// === exports ===

// --- parser ----
module.exports = wasiBuiltin;

function wasiBuiltin() {
  const builtinFuncs = fs.readFileSync(builtinTamplateFile, 'utf-8');
  //println(builtinFuncs);
  return builtinFuncs;
}

fd_write()呼び出し用のパラメータ領域

fd_write()の呼び出しで使うパラメータをメモリ上に確保しておきます。

  • オフセット位置 0バイト目から、4バイト分 ... 実際に出力したバイト数を受け取るための領域
  • オフセット位置 4バイト目から、4バイト分 ... 出力するバイト列の組の最初のアドレスを格納する領域
  • オフセット位置 8バイト目から、4バイト分 ... 出力するバイト列の組の数
  • オフセット位置 12バイト目から、255バイト分 ... 出力するバイト列を格納する領域
function generateMemoryBlock() {
  let block = '';
  block = block + TAB() + '(memory 1)' + LF();
  block = block + TAB() + '(export "memory" (memory 0))' + LF();
  block = block + TAB() + '(data (i32.const 0) "\\00\\00\\00\\00") ;; placeholder for nwritten - A place in memory to store the number of bytes written' + LF();
  block = block + TAB() + '(data (i32.const 4) "\\00\\00\\00\\00") ;; placeholder for iov.iov_base (pointer to start of string)' + LF();
  block = block + TAB() + '(data (i32.const 8) "\\00\\00\\00\\00") ;; placeholder for iovs_len (length of string)' + LF();
  block = block + TAB() + '(data (i32.const 12) "hello world\\n")  ;; 4--> iov.iov_base = 12, 4--> iov_len = 8, 12-->"hello ...":len=13' + LF();

  return block;
}

この領域をputn(), puts()で利用しています。

テンプレートの連結

コンパイラーでWATファイルを生成する際に、ユーザ定義関数に引き続きテンプレートとして用意しておいたputn(), puts()のWATコードを連結して出力します。

function compile(tree, gctx, lctx) {
  // ... 省略 ...

  // ---- global user_defined functions ---
  block = block + generateGlobalFunctions(gctx);

  // ---- builtin function for wasi ---
  block = block + wasiBuiltin();

  // --- close all ---
  block = block + ')';

  return block;
}

WASI向けのコンパイル&実行

今回作ったコンパイラーはこちらです。

これを使って、これまでのサンプルをコンパイル、wasmtimeを使って実行してみましょう。(wasmtimeはテキスト形式の.wat、バイナリ形式の.wasmの両方を実行することができます)

FizzBuffの例

$ node mininode_wasm_wasi.js sample/fizzbuzz_func.js
$ wasmtime generated.wat
1
2
Fizz
4
Buzz
Fizz
7
... 省略 ...
94
Buzz
Fizz
97
98
Fizz
Buzz
$

WASIランタイム上で、無事FizzBuzzを実行できました!

ここまでのソース

GitHubにソースを上げておきます。

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

bind()の第2引数以降で渡す引数と、Functionの呼び出し時に渡す引数はどちらが優先されるのか

JavaScriptのFunctionにはbind()というFunctionをcallした際のthisを指定できるメソッドがありますが、第2引数以降はbindした関数に渡す引数を指定できます。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

関数にbindするときに引数を渡した状態で、その関数を引数ありで呼び出したときにどんな挙動になるのか見てみたところ、bind時に渡した引数が先に渡され、呼び出し時に渡す引数があとに追加されるようでした。

function func(arg1, arg2) {
  console.log(`arg1: ${arg1}`, `arg2: ${arg2}`);
}

func.bind(null, 'bind')('call');
// => arg1: bind arg2: call

DOMのonClickなどにFunctionを渡したときに引数として追加したいときにbindを使うとeventが最後の引数に渡されるのは、Function呼び出し時の引数としてeventを渡す実装になっているのだろうなと想像できました(ソースまで確認はしていません)。

function handleClick(arg, event) {
  console.log(`arg: ${arg}`, `event: ${event}`)
}

let div = document.createElement('div');
div.onclick = handleClick.bind(null, 'bind')
div.click()
// => arg: bind event: [object MouseEvent]

// click()時に handleClick(event) のように呼び出すことで引数の末尾に追加されるのだろう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSのモジュールとbabelとwebpackとは何かまとめてみる(初心者向け)

初心者向けにモダンJavaScriptの開発で必要になる
CommonJSとECMAScriptでのモジュールの違い、babelとwebpackの知識に関してまとめておきます。

CommonJSとECMAScript

JavaScriptの歴史的経緯にもなるのですが、
実はJavaScriptには
サーバサイドのNodeJS(CommonJS)とブラウザのJavaScript(ECMAScript)
の大まかに2つの言語仕様があります。1

元々のJavaScriptの欠点としてJavaScript内でモジュール化されたJavaScriptファイルを外部参照するという言語機能がなかったことが起因しています。
(かつてはHTMLのscriptタグでグローバル参照するというやり方が従来だった)
モジュール機能が先に言語仕様として策定されたのがCommonJSでECMAScriptのモジュール機能は後発となります。
したがって、大まかな文法はほぼ一緒なもののモジュール機能の記述に関して、CommonJSとECMAScript間で大きな違いがあります。
なお、ECMAScriptには仕様策定を決めているTC39という委員会があります。

参考:JavaScriptが辿った変遷

CommonJSとECMAScriptのModulesの違い

細かい違いは以下の記事のほうが参考になります

参考:CommonJS と ES6の import/export で迷うなら

CommonJSのモジュール機能

CommonJSでモジュールを外部参照できるようにするためには、主にmodule.exportsを使います。
ここだとabc.jsというファイル名のモジュールを作成します。

abc.js
module.exports = 変数

CommonJSでモジュールを参照するためには、requireを使います。
requireには参照するモジュールのファイル名を指定します。
abcにはmodule.exportsした変数そのものが参照できます。

const abc = require('abc')

ECMAScriptのモジュール機能

ECMAScriptでモジュールを外部参照できるようにするためには、主にexportもしくはexport defaultを使います。(exportsでないのに注意)
ここだとdef.jsというファイル名のモジュールを作成します。

abc.js
export 変数
export default 変数

ECMAScriptでモジュールを参照するためには、importを使います。
importには参照するモジュールのファイル名を指定します。

import { 変数 } from 'def' // exports 変数を参照する場合
import def from 'def' // exports default 変数を参照する場合
// import { default as def } from 'def' // こうもかける

ちなみにimport文はトップレベルでないと使えないという制限があります。

// トップレベル
import def from 'def' // OK

if (条件分岐) {
  // ブロック{}で囲まれている箇所はトップレベルでない
  import def from 'def' // NG
}

dynamic import

Promise形式で外部モジュールを非同期に参照読み込みすることもできます。(dynamic import)
これにより、うまく使いこなせれば読み込みのオーバヘッドをなくすことができます。(並列読み込みによる高速化)
また、こちらに関してはトップレベルでなくても使えます。

import('def').then(module => { /* 処理 */ })

参考:Chrome、Safari、Firefoxで使えるJavaScriptのdynamic import(動的読み込み)

NodeJSからECMAScript Module(ESM)のimport、exportができるようになる(将来的に)

現在、NodeJS側からESMを参照できるようにするという流れでNodeJSチームが頑張って対応しています。2
Plan for New Modules Implementation

NodeJS 13.2.0から--experimental-modulesフラグ無しでESMのimport、exportが使える

ついにNode 13.2.0(2019/11/21リリース)から--experimental-modules無しでESMのimport、exportが使えるようになりました。(Phase4)
拡張子も.jsのままで.mjsにする必要はなく、package.jsonに"type":"module"の設定を追加するだけでESMのimport、exportが使えます。
公式:ECMAScript Modules

package.json
{
  "type": "module",
  "name": "node13.2",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.19.0",
    "express": "^4.17.1"
  }
}

ESMのimportを使ってNodeJSのサーバを立ててみます。

app.js
import { default as express } from 'express'
import { default as bodyParser } from 'body-parser'
const app = express()

const wrap = (fn) => (req, res, next) => fn(req, res, next).catch(err => {
  console.error(err)
  if (!res.headersSent) {
    res.status(500).json({message: 'Internal Server Error'})
  }
})
process.on('uncaughtException', (err) => console.error(err))
process.on('unhandledRejection', (err) => console.error(err))
process.on('SIGINT', () => process.exit(1))

app.use(express.static('dist'))
app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json())

app.get('/api', wrap(async (req, res) => {
  res.json('hello world!!')
}))

app.listen(3000, () => {
  console.log('Access to http://localhost:3000')
})

警告は出ますが、babel無しでESMのモジュールのimportができるようになりました。

$ node app.js 
(node:62642) ExperimentalWarning: The ESM module loader is experimental.
Access to http://localhost:3000

babel

フロントエンドの実装にはブラウザ間でHTML、CSS、JSの実装レベルが異なるという大きな問題があります。
ブラウザの各機能の実装状況はCan I useなどで確認できます。
この問題に関して新しいJS文法を古いJS文法に変換(トランスパイル)して古いブラウザでも使えるようにするためのツールがbabelです。
babelの役割は非常に多く、多数のプラグインとpresetが存在しています。
例えば以下のようなことが実現できます。

Polyfill参考:Babel 7.4.0で非推奨になった@babel/polyfillを使わず、core-js@3で環境構築する
TypeScript参考:Babel 7でTypeScriptをトランスパイルしつつ型チェックをする 〜webpack 4 + Babel 7 + TypeScript + TypeScript EsLint + Prettierの開発環境を構築する〜

babelの設定ファイル

.babelrc, babel.config.js, babel.config.cjs, babel.config.jsonのいずれかのファイルに設定を書くか
webpackの場合、後述のbabel-loaderプラグインのoptionsに指定する方法があります。
(babel実行時に自動に設定ファイルを探しに行く)
主にpluginと複数のpluginをまとめたpresetを指定することがほとんどです。

公式:Configure Babel

例えば.babelrc、babel.config.jsonの場合は次のように書きます。

{ 
  "plugins": ["@babel/plugin-proposal-optional-chaining"],
  "presets": [
    ["@babel/preset-env", {
       "useBuiltIns": "usage",
       "corejs": 3,
       "modules": false
    }]
  ] 
}

babel.config.js、babel.config.cjsの場合は次のように書きます。

module.exports = function (api) {
  api.cache(true) // この変換設定関数をキャッシュする

  const presets = [
   ["@babel/preset-env", {
      "useBuiltIns": "usage",
      "corejs": 3,
      "modules": false
   }]
  ]
  const plugins = ["@babel/plugin-proposal-optional-chaining"]

  return {
    presets,
    plugins
  }
}

babelの実行

babelのトランスパイルには@babel/core、@babel/cliが必要です。

# package.json作成
$ npm init -y
# babelのコマンドラインツールのインストール
$ npm install @babel/core @babel/cli 
# babelのプラグインのインストール
$ npm install @babel/plugin-proposal-optional-chaining
# polyfill系
$ npm install @babel/preset-env core-js regenerator-runtime

例えば、次のようなindex.jsを作成します。

index.js
const message = {hello: 'Hello', world: 'world!'}

console.log(message?.hello + '' + message?.world)

.babelrcもしくはbabel.config.jsを作成し、次のコマンドを実行します。

$ babel index.js --out-file out.js

変換後のout.jsは次のようになります。
(ES5文法への変換、optional-chaining文法が変換される)

out.js
var message = {
  hello: 'Hello',
  world: 'world!'
};
console.log((message === null || message === void 0 ? void 0 : message.hello) + '' + (message === null || message === void 0 ? void 0 : message.world));

webpack

webpackは、モダンJavaScriptアプリケーション用の静的モジュールバンドルです。
webpackがアプリケーションを処理するとき、プロジェクトが必要とするすべてのモジュールをマップし、1つ以上のバンドルを生成する依存関係グラフを内部で構築します。
要はwebpackでビルドすることでnode_modules以下の依存関係も含め、1つのJSファイルにまとめることができます。
コアなコンセプトとして以下の機能があります。

  • Entry:ビルド対象のアプリケーションエントリーポイントとなるファイル
  • Output:出力先のフォルダ、ファイル名
  • Loaders:ビルド用ローダー(メインの変換処理を行うための設定)
  • Plugins:ビルドの補助プラグイン
  • Mode:development/productionビルドかの指定
  • Browser Compatibility:ブラウザ最適化

公式:Concept

バージョン4.0.0以降、webpackはプロジェクトをバンドルするための設定ファイルを必ずしも必要としません。
ただし、デフォルトの設定を上書きする際はwebpack.config.jsを作成して設定を記述します。

Entry

エントリポイントは、webpackが内部依​​存関係グラフの構築を開始するために使用するモジュールを示します。
webpackは、エントリポイントが依存する他のモジュールとライブラリを(直接的および間接的に)把握します。
デフォルトでは、その値は./src/index.jsですが、webpack構成でentryプロパティを設定することにより、異なる(または複数のエントリポイント)を指定できます。

webpack.config.js
module.exports = {
  entry: './path/to/my/entry/file.js'
};

Output

outputプロパティは、作成したバンドルを出力する場所とこれらのファイルに名前を付ける方法をwebpackに指示します。
デフォルトでは、メイン出力ファイルの場合は./dist/main.js、その他の生成されたファイルの場合は./distフォルダになります。

設定ファイルにて出力先を変更することも可能です。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),  // 出力するフォルダ名
    filename: 'my-first-webpack.bundle.js' // 出力するメインファイル名
  }
};

Loader

すぐに使えるwebpackは、JavaScriptファイルとJSONファイルのみを理解します。Loaderを使用すると、webpackは他の種類のファイルを処理し、アプリケーションで使用して依存関係グラフに追加できる有効なモジュールに変換できます。

大まかに言うと、LoaderにはWebpack構成に2つのプロパティがあります。

  1. testプロパティは、変換する対象のファイルを識別します。
  2. useプロパティは、変換を行うために使用するLoaderを指定します。
webpack.config.js
const path = require('path');

module.exports = {
  output: {
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  }
};

Plugins

Loaderは特定のタイプのモジュールを変換するために使用されますが、
プラグインを活用して、バンドルの最適化、リソース管理、環境変数の注入などの幅広いタスクを実行できます。
プラグインを使用するには、require()してplugins配列に追加する必要があります。
ほとんどのプラグインはオプションでカスタマイズできます。
プラグインはさまざまな目的のためにconfigで複数回使用できるため、new演算子で呼び出してプラグインのインスタンスを作成する必要があります。

webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const webpack = require('webpack'); //to access built-in plugins

module.exports = {
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};

Mode

modeパラメーターをdevelopment、production、またはnoneに設定することにより、
各環境に対応するwebpackの組み込みの最適化を有効にできます。
デフォルト値はproductionです。

webpack.config.js
module.exports = {
  mode: 'production'
};

Browser Compatibility

webpackは、ES5準拠のすべてのブラウザーをサポートしています(IE8以下はサポートされていません)。
webpackでは、import()およびrequire.ensure()にPromiseが必要です。
古いブラウザをサポートする場合は、
これらの文法を使用する前にpolyfillをロードする必要があります。

webpackの実行

次のような簡単なReactのプロジェクト構成でビルドしてみます。

├── dist
├── index.html
├── package.json
├── src
│   └── index.tsx
├── tsconfig.json
└── webpack.config.js

webpackのビルドにはwebpackwebpack-clibabel-loaderのパッケージが追加で必要です。
今回はReactのプロジェクトのため、reactreact-dom@babel/preset-reactを追加します。
さらにtypescriptを使うため、typescript@babel/preset-typescript@types/react@types/react-domのパッケージが必要です。
@babel/preset-typescriptは型チェックをしてくれないため、tscで並列で型チェックを行います。
npm-run-allを使うとrun-pコマンドで並列でコマンドを実行することができます。
webpackコマンドでwebpack.config.jsの設定でプロジェクトをビルドすることができます。
package.jsonは次のようになります。

package.json
{
  "name": "test",
  "version": "1.0.0",
  "license": "MIT",
  "scripts": {
    "start": "run-p check-types webpack",
    "check-types": "tsc -w",
    "webpack": "webpack --watch"
  },
  "devDependencies": {
    "@babel/cli": "^7.7.4",
    "@babel/core": "^7.7.4",
    "@babel/plugin-proposal-optional-chaining": "^7.7.4",
    "@babel/preset-env": "^7.7.4",
    "@babel/preset-react": "^7.7.4",
    "@babel/preset-typescript": "^7.7.4",
    "@types/react": "^16.9.13",
    "@types/react-dom": "^16.9.4",
    "babel-loader": "^8.0.6",
    "core-js": "^3.4.2",
    "html-webpack-plugin": "^3.2.0",
    "npm-run-all": "^4.1.5",
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "regenerator-runtime": "^0.13.3",
    "typescript": "^3.7.2",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.10"
  }
}

index.tsxにReactの最小のプログラムを記載します。
Reactの型に関しては次のサイトが参考になります。

参考:TypeScript Deep Dive 日本語版

index.tsx
import React from 'react'
import ReactDOM from 'react-dom'

const message = {hello: 'Hello', world: 'world!'}

const App: React.SFC<{message: string}> = (props) => {
  return <h1>{props.message}</h1>
}

ReactDOM.render(
  <App message={`${message?.hello} ${message?.world}`} />,
  document.getElementById('root')
)

vscodeの場合、Optinal Chainingのエラー表記が出る場合は以下の設定を行うと解消されます
TypeScriptのOptional Chainingは用法用量を守って正しく使え

typescriptのビルド設定をtsconfig.jsonに記載します。
今回はtypescriptのビルドには@babel/preset-typescriptを使うため、型のチェックのみを行います。
"noEmit": trueが重要)

tsconfig.json
{
  "compilerOptions": {
    /* トランスパイル後のECMAScriptのバージョン */
    "target": "ES2019",

    /* 相対パスではないモジュールは node_modules 配下を検索する */
    "moduleResolution": "node",

    /* 今回、トランスパイルは Babelが行うので、`tsc`コマンドでJavaScriptファイルを出力しないようにする */
    "noEmit": true,

    /* 厳格な型チェックオプション(noImplicitAny、noImplicitThis、alwaysStrict、
       strictBindCallApply、strictNullChecks、strictFunctionTypes、
       strictPropertyInitialization)を有効化する */
    "strict": true,

    /* 各ファイルを個々のモジュールとしてトランスパイルする。
       Babel では技術的制約で、ネームスペースなどのファイルを跨いだ構文を解釈してトランスパイルできない。
       このオプションを有効にすれば、Babel でトランスパイルできない TypeScriptの構文を検出して警告を出す */
    "isolatedModules": true,

    /* ES modules 形式以外の、CommonJS 形式などのモジュールを default import 形式で読み込める
       例)const module = require('module') -> import module from 'module' */
    "esModuleInterop": true,

   /* Reactの場合、JSX文法のチェックを有効にする */
    "jsx": "react"
  },
  "include": ["src/**/*"]
}

webpack.config.jsにwebpackのビルド設定を記述します。

webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')

module.exports = {
  mode: 'development', // 開発モードビルド
  entry: './src/index.tsx', // ビルド対象のアプリケーションのエントリーファイル
  devtool: "source-map", // ソースマップを出力するための設定、ソースマップファイル(.map)が存在する場合、ビルド前のソースファイルでデバッグができる
  output: {
    path: path.resolve(__dirname, 'dist'),  // 出力するフォルダ名(dist)
    filename: 'bundle.js' // 出力するメインファイル名
  },
  module: {
    rules: [
      { 
        test: /\.ts(x?)$/, // .ts .tsxがbabelのビルド対象
        exclude: /node_modules/, // 関係のないnode_modulesはビルドに含めない
        use: {
          loader: 'babel-loader', // babel
          options: {
            presets: [
              // polyfillのpreset
              ['@babel/preset-env', {
                useBuiltIns: 'usage',
                corejs: 3,
                modules: false // ECMAScript向けビルド
              }],
              // reactのpreset
              '@babel/preset-react',
              // typescript→javascript変換のpreset
              '@babel/preset-typescript'
            ],
            // optional-chaining文法を使うためのプラグイン
            plugins: ['@babel/plugin-proposal-optional-chaining']
          },
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({template: './index.html'}) // ビルドしたbundle.jsをindex.htmlに埋め込む
  ]
}

index.htmlは以下のようになります。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>タイトル</title>
</head>
<body>
  <div id='root'></div>
<!-- webpackビルドが完了するとHtmlWebpackPluginによりビルド済みのbundle.jsが埋め込まれる -->
<!-- <script type="text/javascript" src="bundle.js"></script></body> -->
</body>
</html>

ビルドには次のコマンドを実行します。
成功するとdistフォルダにbundle.jsが埋め込まれたindex.htmlが生成されます。

$ npm start

webpack is watching the files…

Hash: e532e63e7e9060154c4e
Version: webpack 4.41.2
Time: 1350ms
Built at: 2019-11-25 17:56:00
        Asset       Size  Chunks                   Chunk Names
    bundle.js    1.1 MiB    main  [emitted]        main
bundle.js.map   1.26 MiB    main  [emitted] [dev]  main
   index.html  326 bytes          [emitted]        
Entrypoint main = bundle.js bundle.js.map
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {main} [built]
[./src/index.tsx] 495 bytes {main} [built]
    + 65 hidden modules
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [./node_modules/html-webpack-plugin/lib/loader.js!./index.html] 484 bytes {0} [built]
    [./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {0} [built]
    [./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 1 hidden module
[17:56:01] Found 0 errors. Watching for file changes.


webpackのミドルウェア

webpackのミドルウェアを導入することができれば、さらに開発の効率があがります。
導入方法だけでそれぞれ記事ができてしまうため、ここでは紹介までにしておきます。

  • webpack-dev-derver:開発用のサーバを起動して、その上でwebpackを実行するためのミドルウェア。HMR(Hot Module Replacement)なども設定でき、プログラム修正時にブラウザをリロードすることなくモジュールの再読み込みを行ってくれる。
  • webpack-dev-middleware:バックエンド(NodeJS)でwebpackビルドができるようになるミドルウェア
  • webpack-hot-middleware:バックエンドのwebpackビルド時にHMRしてくれるミドルウェア

Parcel

webpackに関してはビルドの設定の自由度が高い反面、ビルドの設定が難しいという問題があります。
簡易的なプロジェクトであれば、静的バンドラーの一種であるParcelを使うという選択肢もあります。
Parcelはビルド設定ファイルがなく裏側で色々よしなにやってくれる反面、細かいビルド設定ができないという欠点もあります。
本格的なプロジェクトの場合はwebpackをマスターしたほうが後々良いでしょう。

参考:(兎に角)早くプロトタイプを作る技術(初心者向け)


  1. 正確には亜種なスクリプト仕様はいくつかあるのですが一旦置いときます(Google Apps ScriptとかJScriptとか・・・) 

  2. ESMがNodeJS側で参照できるようになれば、npmであらゆるパッケージがバックエンド、フロントエンドの垣根を超えて使えるようになる未来がくるのかも・・・?(さらにエコシステム化が進む) 

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

JavaScript(Node.js)でエクセルデータ(CSV)からJSONファイルを出力する(都道府県データ)

概要

JavaScript(Node.js)を用いて、excelデータJSONに出力するまでの流れを説明する。

事前準備

ターミナル or コマンドプロンプト
nodeのインストール(https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09)
Excelファイル

Excelファイルは、総務省が掲載している、都道府県コード及び市区町村コード(令和元年5月1日現在)を使用する。
http://www.soumu.go.jp/denshijiti/code.html

手順

  1. csvファイルを作る(prefectures.csv)
  2. jsファイルを作る(prefectures.js)
  3. jsと同じディレクトリにcsvファイルを入れる
  4. csvファイルをコンソールで表示する
  5. バイナリーデータを文字列に変換する
  6. 文字列を配列に変換する
  7. 配列をJSONに変換する
  8. jsonファイルを出力する

データ変換の流れとしては

csv => バイナリーデータ => 文字列 => 配列 => JSON

という順番でJSONファイルを作成することができる

csvファイルを作る(prefectures.csv)

http://www.soumu.go.jp/denshijiti/code.html
からエクセルデータをダウンロードし、 prefectures.csv という名前で保存(名前は自由)
その後、prefectures.csvを開き、ヘッダー部分(1-5行目)を削除する(データを統一するため)

団体コード,"都道府県名
(漢字)","市区町村名
(漢字)","都道府県名
(カナ)","市区町村名
(カナ)",,

csv => バイナリーデータ

csvファイルと同じディレクトリに prefectures.js を作成する
Node.jsには公式モジュールのfsがあるのでそれを使用する。
https://nodejs.org/api/fs.html#fs_file_system

const FS = require('fs')
const PATH = require('path')

const csvPath = PATH.join(__dirname, 'prefectures.csv')

csvファイルのpathを確認する。確認方法は ターミナルnode ファイル名.js コマンドを叩く

$ cd test
$ node prefectures.js
/Users/yourname/Desktop/test/prefectures.csv

readFileSyncでcsvの中身を読み込む。
ファイルの中身を読むにはfs.readFileSyncを用いる。

const csvData = FS.readFileSync(csvPath)
console.log(csvData)
//<Buffer 30 31 30 30 30 36 2c e5 8c 97 e6 b5 b7 e9 81 93 2c 2c ef be 8e ef bd af ef bd b6 ef bd b2 ef be 84 ef be 9e ef bd b3 2c 2c 2c 0d 0a 30 31 31 30 30 32 ... >

しかし、バイナリデータが表示されるので、文字列に直す

バイナリデータ => 文字列

const csvData = FS.readFileSync(csvPath).toString()
console.log(csvData)
// 010006,北海道,,ホッカイドウ,,,

今度は、csvの中身が1つの文字列として表示される。これを配列に変換する。

文字列 => 配列

1つの文字列からデータを分けるために、配列に変換する。

改行コードで区切るようにする

const rows = csvData.split('\n')
console.log(rows)

// [ '010006,北海道,,ホッカイドウ,,,\r',
//  '011002,北海道,札幌市,ホッカイドウ,サッポロシ,,\r',
//  '012025,北海道,函館市,ホッカイドウ,ハコダテシ,,\r',...]

\rが余分に入っているので取り除く(UNIX系のOSでは\nが使われ、Microsoft系のOS(Excel等)では、\r\nが使われるため)

const rows = csvData.split('\r\n')
console.log(rows)
// [ '010006,北海道,,ホッカイドウ,,,',
//  '011002,北海道,札幌市,ホッカイドウ,サッポロシ,,',
//  '012025,北海道,函館市,ホッカイドウ,ハコダテシ,,'...]

これで、1つの配列内に、複数の文字列を格納することができた。
続いて、区切ったものを配列に入れていく。

const prefectures = rows.map(row => {
  return row.split(',')
})
console.log(prefectures)
// [ [ '010006', '北海道', '', 'ホッカイドウ', '', '', '' ],
//   [ '011002', '北海道', '札幌市', 'ホッカイドウ', 'サッポロシ', '', '' ],
//   [ '012025', '北海道', '函館市', 'ホッカイドウ', 'ハコダテシ', '', '' ],...]

これだと、配列の中に配列が入っている状態(多次元配列)なので、1つの配列の中に複数のオブジェクトを作り、JSONにする。

配列 => JSON

const prefectures = rows.map(row => {
  const cols = row.split(',')
  return {
    code: cols[0],
    prefecture: cols[1],
    city: cols[2],
    prefectureKana: cols[3],
    cityKana: cols[4]
  }
})
console.log(prefectures)
//  { code: '011002',
//    prefecture: '北海道',
//    city: '札幌市',
//    prefectureKana: 'ホッカイドウ',
//    cityKana: 'サッポロシ' },///]

writeFileSync を用いてJSONファイルを作成する

const jsonPath = PATH.join(__dirname, 'prefectures.json')
FS.writeFileSync(jsonPath, JSON.stringify(prefectures))

実装に関してはここまで。最後に

$ node prefectures.js

を行い、同じディレクトリに prefectures.json が入っていれば完成。

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

人気の旅行APIをまとめてみた

旅行と観光は、世界最大かつ最も成長著しい産業の1つです。国連世界観光機関(UNWTO)によると、2016年には1950年の2,500万人から12億人もの人が外国に到着しました。専門家によると、この業界は今後もさらに成長すると予想されます。

この成長により、旅行会社はAPI(アプリケーション・プログラミング・インターフェイス)を公開し、開発者がAPIの機能をアプリケーションに統合できるようになりました。

この記事では、現在使用されている人気の旅行APIのリストを作成しました。このリストを作成するにあたり、次の基準に基づいてAPIをランク付けしました。

API機能:これらのAPIの最も高度な機能のいくつかを詳しく調べました。
人気:各プラットフォームの人気を確認しました。これは、Webサイトまたはアプリケーションに統合するのに最適なプラットフォームを決定するのに役立ちます。
使いやすさ:各APIをアプリケーションに統合する容易さを評価しました。
価格:各APIの使用コストを評価しました。

0Travel.png

最終的に次の人気の旅行APIトップ10リストを作成しました。
Booking API
Kayak
Trail API
Webcams.travel API
Great Circle Mapper API
Skyscanner API
Hipmunk API
Hotels in Singapore API
Flight Data API
World IATA Airports API

人気の旅行APIトップ10
調査結果をまとめた表です

API API機能 人気度 使いやすさ 価格
Booking API 開発者は、利用可能な宿泊施設、価格、不動産施設、不動産ポリシーなどの詳細について公式ウェブサイトに問い合わせることができます 40以上の言語で利用可能で、世界200か国以上の150,000以上の目的地をカバーしています 使いやすい 月額20ドルから300ドルまでの無料プランとさまざまな有料プラン
Kayak API 位置情報、実際のフライトのルートと価格、ホテルの予約、車の予約を照会できます 世界中で利用可能 使いやすい 月額20ドルから300ドルまでの無料プランとさまざまな有料プラン
Trail API いくつかのアウトドアのレクリエーションの目的地の情報と写真を入手します 人気上昇中 使いやすい 月額5ドルから250ドルまでの無料プランとさまざまな有料プラン
Webcams.travel API 大陸、国、近くの位置、地域、カテゴリなど、幅広い基準に従ってウェブカメラを検索およびフィルタリングできます 世界中で利用可能 使いやすい 無料
Great Circle Mapper API 世界中の空港間の飛行距離と飛行時間を計算します 世界中で利用可能 使いやすい 月額100ドルまたは200ドルの無料プランと有料プラン
Skyscanner API フライトスケジュール、フライト価格、代理店からのライブチケット見積もりを取得 世界中で利用可能 使いやすい 無料
Hipmunk API リアルタイムのフライト価格、フライト予約、ホテル予約、空港コードを取得 世界中で利用可能 使いやすい 月額20ドルから300ドルまでの無料プランとさまざまな有料プラン
Hotels in Singapore API ホテルの名前、利用可能な部屋、住所、場所、ソーシャルメディアの詳細を取得する シンガポールでのみ利用可能 使いやすい 無料
Flight Data API 選択したルートの最も安いノンストップチケット、指定された都市の最も人気のあるルート、人気のある航空会社のルートなどを取得します 人気上昇中 使いやすい 無料
World IATA Airports API IATAコードを使用してすべての空港の信頼できるリストを取得し、IATAコードに基づいて空港の詳細を取得します 世界中で利用可能 使いやすい 月額19ドルの無料プランと有料プラン

1.Booking API

1Travel.png
Booking.comは、世界中の宿泊予約サービスを幅広く提供することにより、旅行の摩擦をなくすことを目的としたeコマースWebサイトです。

APIの機能:Booking APIを使用すると、利用可能な宿泊施設、価格、施設、施設のポリシー、施設の場所、為替レート、注目のホテルのレビューなどの詳細を公式Webサイトに照会できます。

人気:Booking.comサービスは40以上の言語で利用可能で、世界200か国以上の150,000以上の目的地をカバーしています。 また、2800万件以上の報告されたリストを提供しています。

使いやすさ:詳細なドキュメントがあり、数分以内にAPIを簡単に起動して実行できます。

価格:1日に500件未満のリクエストを行う限り、APIを無料で使用できます。 より高い制限にアクセスするには、月額20ドルから月額300ドルの範囲の有料プランを選択できます。

2.Kayak API

2Travel.png
Booking.comの一部であるKayakは、旅行者が何百もの他の旅行Webサイトを検索し、フライト、バケーションパッケージ、レンタカー、およびホテルの面で最高のお得な情報を入手できるWebサイトです。

API機能:Kayak APIを使用すると、旅行メタ検索エンジンの機能を統合して、ユーザーが自信を持って旅行を決定できるように支援できます。 APIを使用すると、位置情報、ライブのフライトルートと価格、ホテルのライブ予約、車のライブ予約を照会できます。

人気度:カヤックは世界中の旅行者にサービスを提供しています。 毎年、旅行情報に関連する20億件以上の検索を処理します。

使いやすさ:Kayakは、開発者がAPIを簡単に統合できるようにする包括的なドキュメントを提供します。

価格:無料サブスクリプションプランは、1か月あたり500件のリクエストに制限されています。 無料プランの他に、月額20ドルから月額300ドルまでの有料プランを選択できます。

3.Trail API

3Travel.png

Trail APIを使用すると、開発者は屋外レクリエーションの場所の幅広いコレクションにアクセスでき、旅行者はそれを使用して情報に基づいた意思決定を行うことができます。

API機能:Trail APIを使用すると、屋外のレクリエーションの目的地の情報と写真を取得して、アプリケーションに統合できます。 たとえば、マウンテンバイクトレイル、ATVトレイル、スキーリゾート、キャンプ場などの説明、写真、評価を取得できます。

人気:Trail APIは、その優れた機能を主な理由として、急速に人気が高まっています。

使いやすさ:このAPIは、活発な開発者コミュニティとともに文書化されています。 したがって、統合の課題については、迅速に支援を受けることができます。

価格:1日に500件未満のリクエストを行う場合は、APIを無料で使用できます。 無料プランを超えて、有料プランは月額5ドルから月額250ドル以上の範囲です。

4.Webcams.travel API

4Travel.png
Webcams.travelは、視覚的な観光地をアプリやウェブサイトに追加するために使用できるウェブカメラの膨大なディレクトリを提供します。

Webcams.travelは、視覚的な観光地をアプリやウェブサイトに追加するために使用できるウェブカメラの大きなディレクトリを提供します。

API機能:Webcams.travel APIを使用すると、Webカメラのリポジトリにプログラムでアクセスできます。 APIを使用すると、大陸、国、近くの場所、地域、カテゴリなど、さまざまな基準に従ってウェブカメラを検索およびフィルタリングできます。 すべてのウェブカメラについて、タイムラプススライドショーを取得することもできます。

人気:Webcams.travelには、世界最大のウェブカメラのコレクションがあります。 現在、世界中のさまざまな場所に74,000を超えるランドスケープWebカメラがあります。 さらに、毎日約50のWebカメラがリポジトリに追加されます。

使いやすさ:Webcams.travelは、APIの迅速かつシームレスな統合を促進するために、詳細なドキュメントとその他の有用な開発者向けリソースを公開しています。

価格:APIは無料で提供されます。

5.Great Circle Mapper API

5Travel.png

Great Circle Mapperは、世界中の空港間の飛行距離と飛行時間を計算するために使用されるサービスです。

API機能:Great Circle Mapper APIを使用すると、2つ以上の空港のコードを入力して、地図上に飛行経路を視覚化し、それらの間の距離を海里とキロメートルで計算できます。 APIを使用して、任意の航空機の飛行時間(任意の速度)、空港レコードのリスト、および航空機に関する情報を取得することもできます。

人気:世界中のすべての空港で利用できます。

使いやすさ:このAPIは、成長を続ける開発者コミュニティとともに文書化されています。これにより、統合が簡単です。

価格:APIの無料版は1日あたり10リクエスト未満に制限されています。 それ以外の場合は、月額100ドルまたは月額200ドルを支払い、より高い制限にアクセスできます。

6.Skyscanner API

6Travel.png
Skyscannerは検索プラットフォームであり、ユーザーはホテル、フライト、レンタカーの一番お得な情報を利用できます。

APIの機能:Skyscanner APIを使用すると、Skyscannerの巨大なデータベースを照会し、フライトスケジュール、価格、代理店からのライブチケット見積もりを取得できます。 さらに、APIを使用して、クエリ文字列に一致する場所のリストを取得したり、最も安いフライトルートを取得したり、為替レートを取得したりできます。

人気:このプラットフォームは30を超える言語で利用でき、毎月世界中で1億人以上の人々が使用しています。

使いやすさ:Skyscannerには、活発な開発者コミュニティと詳細なドキュメントがあります。

価格:このAPIは無料で使用できます。

7.Hipmunk API

7Travel.png
Hipmunkは、いくつかの主要な旅行および航空会社のサイトを検索するサービスで、ユーザーは完璧なフライトまたはホテルを最低価格で入手できます。

APIの機能:Hipmunk APIを使用すると、世界中の主要な航空会社やプロバイダーからリアルタイムのフライト価格、フライト予約、ホテル予約、その他の旅行情報を引き出すことができます。 APIを使用して、空港コードやその他の場所の詳細を検索することもできます。

人気:Hipmunkは200万件以上のホテルと賃貸物件をリストしています。

使いやすさ:Hipmunkは、包括的なドキュメントとその他のリソースを提供して、WebまたはモバイルアプリケーションへのAPIの実装を容易にします。

価格:1か月あたり500件未満のリクエストを行う場合は、APIを無料で使用できます。 アクセス制限が高い有料プランの価格は、月額20ドルから月額300ドルです。

8.Hotels in Singapore API

8Travel.png

シンガポールのホテルAPIは、ユーザーが国内のホテルを簡単に見つけられるように、シンガポールのホテルライセンス委員会からデータを収集します。

APIの機能:Hotels in Singapore APIを使用すると、名前、都市、または郵便番号でホテルを検索できます。 その結果、ホテルの名前、利用可能な部屋、住所、場所、ソーシャルメディアの詳細に関する情報を取得できます。

人気:シンガポールでのみ利用可能です。

使いやすさ:数分以内にAPIを簡単に起動して実行できるようにする簡単なドキュメントがあります。

価格:無料で提供されます。 認証も必要ありません。

9.Flight Data API

9Travel.png

TravelpayoutsのFlight Data APIを使用すると、開発者は旅行に関する洞察を取得し、それらをWebサイトまたはブログに統合できます。

APIの機能:Flight Data APIを使用すると、選択したルートの最も安いノンストップチケット、指定した都市の最も人気のあるルート、人気のある航空会社のルート、1つまたは2つの停留所がある最も安いチケット、月ごとにグループ化した最も安いチケットを取得する事が可能です。

人気:APIの人気は急速に高まっています。

使いやすさ:さまざまなプログラミング言語でのクエリと回答のサンプルを含む詳細なドキュメントがあります。

価格:無料で提供されています。

10.World IATA Airports API

10Travel.png

国際航空運送協会(IATA)は、117か国の290の主要航空会社で構成される評判の良い協会です。これは、世界の航空業界でポリシーと標準を策定する上で極めて重要な役割を果たします。

APIの機能:World IATA Airports APIを使用すると、IATAコードを使用してすべての空港の信頼できるリストを取得できます。 IATAコードに基づいて空港の詳細を取得することもできます。これにより、どの空港が最高水準の効率とセキュリティを維持しているかを知ることができます。

人気:世界中の主要な空港に関する情報を入手できます。

使いやすさ:APIを簡単に使用するのに役立つシンプルなドキュメントがあります。

価格:空港の完全なリストを無料で入手できますが、月に1回のリクエストに制限されます。空港の詳細を無料で取得することも、月に10件のリクエストで制限されています。無制限のリクエストを行うには、月額19ドルの有料プランを利用できます。

これがRakuten RapidAPI人気の旅行APIトップ10です。旅行機能をアプリケーションに追加するために使用できるAPIを見つけていただく事ができれば幸いです。

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

bindを複数回使用するとthisは最初にbindしたオブジェクトになる

JavaScriptのFunctionにはbind()というFunctionをcallした際にthisを指定できるメソッドがあります。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

bind()複数回呼び出したときにどんな挙動になるのか見てみたところ、最初にbind()に渡したオブジェクトがthisになるようです。

class First {};
class Second {};
function multipleBind() {
  console.log(`this: ${this.constructor.name}`)
};

multipleBind.bind(new First()).bind(new Second())()
// => this: First

ただ、bind()の第2引数以降は呼び出す関数に渡す引数を指定できますが、こちらは複数回bindを渡すとその分引数が追加されていくようです。

class First {};
class Second {};
function multipleBind(arg1, arg2) {
  console.log(`this: ${this.constructor.name}`, `arg1: ${arg1}`, `arg2: ${arg2}`)
};

multipleBind.bind(new First(), '111').bind(new Second(), '222')()
// this: First arg1: 111 arg2: 222

bindは1回にしましょう..

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

Hamlの基本箇所についての復習④(ネスト)

はじめに

飽きもせずHamlの基礎について書いていきます。
今回はクラスのネストについてです。

実行

例えば、htmlで次の様に書かれているとします。

<div ="wrapper">
 <div ="top">
 </div>
 <div ="down">
 </div>
</div>

wrapperという大きなクラスがあり、その中にtopdownという小さなクラスが二つあります。
このように親要素・子要素があるhtmlファイルだと、ちょっとした書き間違えが起こります。例えば次のようなものです。

<div ="wrapper">
 <div ="top">
 <div ="down">
 </div>
</div>

これは<div ="top">の閉じタグがない状態です。これだとCSSがちゃんと適用されないなどのエラーが起きます。
単純なミスですが、意外と気づきにくく、エラー解決まで時間を要することも少なくないです。

しかし、Hamlを使えばこの問題を多少なり解決することができます。
前述のコードをHamlで書き直すと次のようになります。

.wrapper
  .wrapper__top
  .wrapper__down

htmlよりも短く書けたのが分かると思います。
一番わかり易い違いは閉じタグがないことです。
クラス名を書いた後に、本文を書くだけでオブジェクトが作れます。
これなら閉じタグを書き忘れた事によるエラーが起きえません。

もう一つはクラスの書き方です。
htmlでは、親要素には親要素のクラスのみ、子要素には子要素のクラスのみを書いていましたが、
Hamlでは子要素には親要素を含めたクラスを書きます。
書き方は.親要素のクラス名__子要素のクラス名と書きます。間に_(アンダーバー)を二つ続けて書きます。
上記のコードだと、親要素がwrapper子要素がtop,downなので、

  .wrapper__top
  .wrapper__down

と書きます。
また、子要素は親要素から半角2文字分のインデントをつけます
そうしないとエラーが起きて、ビューが表示されません。
これにさらに子要素を追加する、例えばtopの子要素としてright,leftをつける場合は、

.wrapper
  .wrapper__top
    .wrapper__top__right
    .wrapper__top__left
  .wrapper__down

という風に書けます。
ちなみに、これをhtmlで記述すると

<div ="wrapper">
 <div ="top">
  <div = "right">
  </div>
  <div = "left">
  </div>
 </div>
 <div ="down">
 </div>
</div>

となります。

注意点としまして、例えば次のように途中のクラス名が間違っていると不具合が起きます。

.wrapper
  .wrapper__top
    .wrapper__tap__right
    .wrapper__tap__left
  .wrapper__down

子要素のクラス名で、親要素のクラスがtopにも拘わらずtapと誤って記述されています。
こうなると、親要素とは全く関係ない要素と判断され、CSSやJSなどで不具合が出てきます。
特にJSの場合、関数が上手く発火してくれない原因の多くがクラス名を間違って書いているだったりする事が多いです。

Hamlは便利ですが、エラーが全く起きないわけではないので、記述する時は最新の注意が必要です(これはHamlに限ったことではありませんが)

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

ES6の学習記録

はじめに

未来電子テクノロジーでインターンをしているやっきーです。

プログラミング初心者であるため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するのでどんどん指摘してください。

ES6とは

javascriptのバージョンの一種です。ES2015とも呼ばれています。(ESとは、ECMAScriptの略です。)

主な記法

表示

console.log(); // ()の中に表示したい文字列、変数を入れる

変数宣言

以前はvarのみが使われていましたが、ES6からconst letが使えるようになりました。主な違いは以下の通りです。

var a; //再代入、再宣言可能(再宣言すると値は上書きされる)
let b; //再代入可能
const c = 100; //共に不可能、初期値を必ず設定しないといけない

比較

==は比較する型が異なる時に型変換を行います。(123=="123"はtrue)
===は型変換を行わない書き方です。型の異なる値を比較するとfalseになります。(123=="123"はfalse)

計算

a = 1000;
b = 200;
console.log(a+b);

とすると、1000200と表示されることがあります。これは、aとbが数字として認識されておらず、文字列の連結として処理してしまっているためです。
ここでは、整数値として認識してほしいので、parseInt()を使います。

a = 1000;
b = 200;
console.log(parseInt(a)+parseInt(b));

と記述すれば、正しく表示されるはずです。

また、小数の演算をしたい場合は、parseFloat()を使用します。

文字列の反転

文字列を反転させる時は、以下のように記述します。sは元の文字列、tは反転後の文字列です。

t = s.split("").reverse().join("");

手順としては、文字列を分割→逆順にする→結合 となります。

配列.includes(要素)

特定の文字が含まれているかを調べる関数です。

var nums = [2,3,4,5,6];
var a = nums.includes(3); //3はnumsに含まれる
var b = nums.includes(9); //9はnumsに含まれない

とすると、aはtrue、bはfalseとなります。

曜日を取得する

年、月、日を入力して曜日を求めるには以下のように記述します。
.getDay()は曜日を数字で返すので文字で出力したい場合は変換する必要があります。

var date = ;//日を代入
var i = date.getDay(); //与えられた日の曜日を0~6の数字で表現
var WeekChars = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]; //曜日名を格納する配列を用意
var dayName = WeekChars[i]; //数字を曜日名に変換

クラス

クラスは、ES6から使えるようになりました。(それまでは、関数をクラス代わりに使っていました。)

宣言

クラスの宣言は以下のように書きます。

class (クラス名) {

    //コンストラクタを定義
    //メソッドを定義
}

コンストラクタの作成

コンストラクタはインスタンスの生成時に呼び出されるメソッドです。

class (クラス名) {

    constructor(x, y, z){
       this.x = x; //xをこのオブジェクトのプロパティーにする
       this.y = y;
       this.z = z;
      }

    //メソッドを定義
}

ここでは、インスタンスの生成時に引数としてx,y,zを受け取り、オブジェクトのプロパティーに代入します。

メソッドを定義する

メソッドとは、オブジェクトの中で定義される関数です。
コンストラクタで設定したプロパティーを使って処理することも出来ます。
以下のコードでは、3つの引数の合計を表示しています。

class Calculate {
  constructor(x, y, z) {
    this.x = x; //xをこのオブジェクトのプロパティーにする
    this.y = y;
    this.z = z;
  }
  Sum() {
    return this.x + this.y + this.z;
  }
}

const result = new Calculate(2, 3, 5); //インスタンス生成
console.log(result.Sum()); //Sumメソッドの返り値を出力
/*出力は10*/

参考URL

javascriptの足し算の演算は変数を数値型にしてから|M2NET
【JavaScript入門】すぐわかる!includesメソッドを徹底解説 | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト
JavaScript - 文字列や数値でオブジェクト型を使う意味|teratail
変数と宣言 · JavaScript Primer #jsprimer
JavaScriptで日時、曜日を取得する方法
【JavaScript入門】class構文の使い方・書き方が分かるようになる方法! | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

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

ajaxのPOST送信で403が返却される場合の対処方法

  • ログイン後、ajaxでPOST送信した時に403が返却される場合、CSRFをリクエストヘッダーに設定すればPOST送信が出来るようになる
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<script type="text/javascript" src="js/jquery-3.4.1.min.js"></script>
<script type="text/javascript">
$(function() {
    let token = $("meta[name='_csrf']").attr("content");
    let header = $("meta[name='_csrf_header']").attr("content");
    $(document).ajaxSend(function(e, xhr, options) {
        xhr.setRequestHeader(header, token);
    });
    $(document).off('click', '#hoge').on('click', '#hoge', function() {
        $.ajax({
            type : "post",
            //type : "get",
            url : "api/hoge",
            contentType : "application/json"
        })
        .done( (result) => {
            alert(result.msg);
        })
    })
});
</script>
</head>
<body>
    <input type="button" id="hoge" value="ボタン" />
</body>
<meta th:name="_csrf" th:content="${_csrf.token}"/>
<meta th:name="_csrf_header" th:content="${_csrf.headerName}"/>
</html>

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

シンプルなルールのAI五目並べ ソースコード

以前投稿したものを改良しました。
石を置けるのは、すでにある石の周りの8箇所だけです(先手の1手目は自由に置けます)。その他に禁じ手はありません。

追記

実際に動くデモ

スクリーンショット

Screenshot

ソースコード

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta name="viewport" content="width=device-width">
<title>五目並べ(13路盤)</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
  <div>
    <div class="nav">
      <a href="/">home</a>
    </div>
    <div class="main">
      <h1>五目並べ(13路盤)</h1>
      <div class="app" id="app">
        <p>
        三三、四四、長連ルールのない単純な五目並べです。2手目以降は既に打った石の周りの空いている場所に打つことができます。
        </p>
        <div class="d_set">
          NEW GAME
          <input type="button" value="1 簡単" class="btn_b1">
          <input type="button" value="2 難しい" class="btn_b2">
        </div>
        <div class="d_start">
          <input type="button" value="●先手" class="btn1">
          <input type="button" value="◯後手" class="btn2">
          で対局開始
        </div>
        <div class="d_stop">
          <input type="button" value="対局を終了する" class="btn_e1">
          <span class="s_1"></span>
        </div>
        <div class="main_disp">
          <div class="d_board" data-size_h="13"  data-size_w="13">
            <table>
              <tbody>

              </tbody>
            </table>
          </div>

        </div>
        <div class="d_ctrl ctrl_iblock">
          <input type="button" value="⏪ 待った" class="btn_a1">
          <input type="button" value="? 履歴" class="btn_a2">
          <input type="button" value="tweet" class="btn_a3">
        </div>
        <div class="outer_msg">
          <div class="msg" data-center="-1">
          </div>
        </div>
        <p>
          (2019/11/25 更新)
        </p>
      </div>
    </div>
    <div class="footer">
    </div>
  </div>
  <script src="js/main.js" defer></script>
</body>
</html>
js/main.js
var deb = null;

window.onload = function(){
  "use strict";
  var data;
  var d_set;
  var d_start;
  var d_ctrl;
  var d_stop;
  var s_1;
  var g_state = [0,"ini",""];
  var table;
  var turn;
  var size_h, size_w;
  var user = [0,1,0];
  var com = [0,0,1];
  var timer;
  var msg;
  var n = 5;
  var mdepth = 2;
  var lastput = [];
  var score_limit = 9999999;
  var rule = {range:0, kinsh:0};
  var uwin;
  var history;

  function init(){
    var d_board = document.querySelector("#app .d_board");
    size_h = +d_board.dataset.size_h;
    size_w = +d_board.dataset.size_w;
    d_set = document.querySelector("#app .d_set");
    d_start = document.querySelector("#app .d_start");
    d_ctrl = document.querySelector("#app .d_ctrl");
    d_stop = document.querySelector("#app .d_stop");
    s_1 = document.querySelector("#app .s_1");
    var btn_b1 = document.querySelector("#app .d_set .btn_b1");
    var btn_b2 = document.querySelector("#app .d_set .btn_b2");
    btn_b1.onclick = function(){
      clearInterval(timer);
      mdepth = 1;
      g_state = [1,"ini","先手または後手を選択してください。"];
      s_1.innerText = "難易度 1";
      render();
    }
    btn_b2.onclick = function(){
      clearInterval(timer);
      mdepth = 1;
      g_state = [1,"ini","先手または後手を選択してください。"];
      s_1.innerText = "難易度:2";
      render();
    }
    var btn1 = document.querySelector("#app .d_start .btn1");
    var btn2 = document.querySelector("#app .d_start .btn2");
    btn1.onclick = function(){
      clearInterval(timer);
      user = [0,1,0];
      com = [0,0,1];
      s_1.innerText = "先手";
      init_game();
    }
    btn2.onclick = function(){
      clearInterval(timer);
      user = [0,0,1];
      com = [0,1,0];
      s_1.innerText = "後手";
      init_game();
    }
    var btn_e1 = document.querySelector("#app .d_stop .btn_e1");
    btn_e1.onclick = stop_game;
    var btn_a1 = document.querySelector("#app .d_ctrl .btn_a1");
    btn_a1.onclick = undo;
    var btn_a2 = document.querySelector("#app .d_ctrl .btn_a2");
    btn_a2.onclick = function(){
      if(history){
        history=false;
      }else{
      history=true;        
      }
      render();
    };
    var btn_a3 = document.querySelector("#app .d_ctrl .btn_a3");
    btn_a3.onclick = tweet;
    table = document.querySelector("#app .d_board table");
    msg = document.querySelector("#app .msg");
    msg.onclick = function(){
      msg.dataset.center = "-1";
    }
    var str = "";
    for(var i = 0; i < size_h; i += 1){
      str += "<tr>";
      for(var j = 0; j < size_w; j += 1){
        var str2;
        if(i === 0 && j === 0){
          str2 = "'00'";
        }else if(i === 0 && j === size_w-1){
          str2 = "'01'";
        }else if(i === size_h-1 && j === size_w-1){
          str2 = "'11'";
        }else if(i === size_h-1 && j === 0){
          str2 = "'10'";
        }else if(i === size_h-1){
          str2 = "'1x'";
        }else if(i === 0){
          str2 = "'0x'";
        }else if(j === size_w-1){
          str2 = "'x1'";
        }else if(j === 0){
          str2 = "'x0'";
        }else if(((i === 3 || i === size_h-4) && (j === 3 || j === size_w-4)) || (i === Math.floor(size_h/2) && j === Math.floor(size_w/2))){
          str2 = "'xx1'";
        }else{
          str2 = "'xx'";
        }
        str += "<td data-bg="+str2+" data-i="+i+" data-j="+j+"></td>";
      }
      str += "</tr>";
    }
    table.innerHTML = str;
    timer = -1;
    g_state = [1,"ini","先手または後手を選択してください。"];
    var app = document.getElementById("app");
    app.style.display = "block";
    render();
  }

  function init_game(){
    data = [];
    for(var i = 0; i < size_h; i += 1){
      data[i] = [];
      for(var j = 0; j < size_w; j += 1){
        data[i][j] = 0;
      }
    }
    g_state = [1,"start","ゲームを開始しました。"];
    lastput = [];
    turn = 1;
    uwin = false;
    msg.dataset.blink = "0";
    clearTimeout(timer);
    timer = -1;
    if(com[turn]){
      timer = setTimeout(com_move, 10);
    }
    render();
  }

  function stop_game(){
    if(timer !== -1){
      return;
    }
    g_state = [1,"stop","ゲームを終了しました。"];
    render();
  }

  function tbl_click(e){
    if(!user[turn]){
      return;
    }
    if(timer !== -1){
      return;
    }
    var i, j;
    i = e.target.parentNode.rowIndex;
    j = e.target.cellIndex;
    if(i != null && j != null && data[i][j] === 0 && (is_legal(data, i, j, turn) || count_data(data) === 0)){
      var ht = [null,[0,0,0,0,0, 0,0,0,0,0],[0,0,0,0,0, 0,0,0,0,0]];
      data[i][j] = turn;
      if(eval_board(data, turn, ht)){
        timer = setTimeout(function(){user_move(i,j)}, 100);
      }
      data[i][j] = 0;
    }else{
      return;
    }
  }

  function user_move(i,j){
    put(i,j);
    g_state = [1,"play",""];
    var tmp = [];
    judge(data, turn, tmp);
    if(tmp[0]){
      msg.innerText = tmp[1];
      g_state = [1,"game_over",tmp[1]];
      timer = -1;
      render(true);
      return;
    }
    turn = turn === 1 ? 2 : 1;
    if(com[turn]){
      timer = setTimeout(com_move, 500);
    }
    render(true);
  }

  function com_move(){
    var ij = think(data, turn);
    timer = -1;
    put(ij[0], ij[1]);
    var tmp = [];
    judge(data, turn, tmp);
    if(tmp[0]){
      msg.innerText = tmp[1];
      g_state = [1,"game_over",tmp[1]];
      render(true);
      return;
    }
    turn = turn === 1 ? 2 : 1;
    render(true);
  }

  function judge(data, p, result){
    var cp = eval_move(data, p);
    var ht = [null,[0,0,0,0,0, 0,0,0,0,0],[0,0,0,0,0, 0,0,0,0,0]];
    eval_board(data, p, ht, deb);
    var arr_score = [null, 0, 0];
    var q = p===1?2:1;
    countup(ht, arr_score, p, q);
    var score = arr_score[p];
    if(score >= score_limit && user[p]){
      result[0] = true;
      result[1] = "あなたの勝ちです";
      uwin = true;
    }else if(score >= score_limit && com[p]){
      result[0] = true;
      result[1] = "コンピューターの勝ち";
      uwin = false;
    }else if(cp.length === 0){
      result[0] = true;
      result[1] = "引き分け";
    }else{
      result[0] = false;
    }
  }

  function eval_move(data, p, deb){
    var cp = [];
    var h = data.length, w = data[0].length;
    var si = 0, sj = 0, cgi, cgj, sv = 0, ss = 0;
    var ch = (h-1)/2;
    var cw = (w-1)/2;
    var is5 = false;

    for(var i = 0; i < h; i += 1){
      for(var j = 0; j < w; j += 1){
        var t = data[i][j];
        if(t === 0){
          var ts = is5s(data, i, j, p);
          if(ts[0] === n){
            return [[i, j, score_limit]];
          }
          if(ts[1] > 0){
            cp.push([i, j, ts[0], ts[1], ts[2]]);
          }
        }else{
          si += i-ch;
          sj += j-cw;
          sv += 1;
        }
      }
      if(is5){
        break;
      }
    }

    var x1, x2, x3, x4, g, mg = -9999, mgi = 0, ng = 9999, ngi = 0;
    if(sv > 0){
      cgi = si/sv+ch;
      cgj = sj/sv+cw;
      for(var i = 0; i < cp.length; i += 1){
        x1 = -(Math.abs(cgi-cp[i][0]) + Math.abs(cgj-cp[i][1]))/4;
        x2 = 0;
        x3 = cp[i][4];
        x4 = Math.random()/16;
        g = cp[i][3] = (x1+x2+x3+x4)/1024;
        if(deb){
          cp[i][4] = [x1,x2,x3,x4];
          if(g > mg){
            mg = g;
            mgi = i;
          }else if(g < ng){
            ng = g;
            ngi = i;
          }
        }
      }
    }

    if(deb){
      console.log(cp[ngi], cp[mgi]);
    }

    if(cp.length === 0){
      var gi, gj;
      gi = Math.floor((size_h-1)/2);
      gj = Math.floor((size_w-1)/2);
      if(data[gi][gj] === 0){
        return [[gi,gj]];
      }else{
        return cp;
      }
    }
    return cp;
  }

  function think(data, p){
    var q = p === 1 ? 2 : 1;
    var sc;
    var mi = 0;

    var cp = eval_move(data, p, deb);
    if(cp[0][2] >= score_limit){
      return cp[0];
    }
    var data2 = dcopy(data);
    var md_p = cp.length >= 19 ? mdepth : (mdepth+0);
    var m = -score_limit;
    var alpha = -score_limit;
    var beta = score_limit;
    var mi = 0;
    cp.sort(sf2);
    if(md_p > 0){
      for(var i = 0; i < cp.length; i += 1){
        data2[cp[i][0]][cp[i][1]] = p;
        sc = -search(data2, q, md_p, -beta, -alpha);
        data2[cp[i][0]][cp[i][1]] = 0;
        if(sc > m){
          m = sc;
          mi = i;
          alpha = Math.max(alpha, m);
        }
        if(sc >= score_limit){
          return cp[i];
        }
        if(alpha >= beta){
          break;
        }
      }
    }else{
      mi = 0;
    }
    return cp[mi];
  }

  function search(data, p, depth, alpha, beta){
    var q = p === 1 ? 2 : 1;

    var ht = [null,[0,0,0,0,0, 0,0,0,0,0],[0,0,0,0,0, 0,0,0,0,0]];

    if(depth > 0){
      var cp = eval_move(data, p);
      if(cp[0][2] >= score_limit){
        return score_limit;
      }
      var data2 = dcopy(data);
      var sc;
      var m = -score_limit;
      cp.sort(sf2);
      for(var i = 0; i < cp.length; i += 1){
        data2[cp[i][0]][cp[i][1]] = p;
        sc = -search(data2, q, depth-1, -beta, -alpha);
        data2[cp[i][0]][cp[i][1]] = 0;
        if(sc > m){
          m = sc;
          alpha = Math.max(alpha, m);
        }
        if(sc >= score_limit){
          return score_limit;
        }
        if(alpha >= beta){
          break;
        }
      }
      return m;
    }else if(depth === 0){
      eval_board(data, p, ht);
      var score = [null, 0, 0];
      countup(ht, score, p, q);
      if(score[p] >= score_limit){
        return score_limit;
      }else if(score[q] >= score_limit){
        return -score_limit;
      }
      return score[p]-score[q];
    }
  }

  function sf1(a, b){
    if(a[2] > b[2]){
      return -1;
    }else if(a[2] < b[2]){
      return 1;
    }else{
      if(a[3] > b[3]){
        return -1;
      }else if(a[3] < b[3]){
        return 1;
      }
      return 0;
    }
  }

  function sf2(a, b){
    if(a[3] > b[3]){
      return -1;
    }else if(a[3] < b[3]){
      return 1;
    }else{
      return 0;
    }
  }

  function is_legal(data, i0, j0, p, deb){
    if(rule.range === 0){
      var di = [-1,-1,-1, 0, 1, 1, 1, 0];
      var dj = [-1, 0, 1, 1, 1, 0,-1,-1];
      var h = data.length, w = data[0].length;
      var ps = 0;
      for(var k = 0; k < 8; k += 1){
        var vi = i0+di[k];
        var vj = j0+dj[k];
        if(vi >= 0 && vi < h && vj >= 0 && vj < w){
          if(data[vi][vj] !== 0){
            ps += 1;
          }
        }
      }
      return ps;
    }else{
      var h = data.length, w = data[0].length;
      var ps = 0;
      var vi, vj;
      for(var i = -2; i < 2; i += 1){
        for(var j = -2; j < 2; j += 1){
          vi = i + i0;
          vj = j + j0;
          if(vi >= 0 && vi < h && vj >= 0 && vj < w && data[vi][vj] !== 0 && !(vi === i0 && vj === j0)){
            ps += 1;
          }
        }
      }
      ps *= 0.25;
      return ps;
    }
  }

  function is5s(data, i0, j0, p){
    var di = [-1,-1,-1, 0, 1, 1, 1, 0];
    var dj = [-1, 0, 1, 1, 1, 0,-1,-1];
    var h = data.length, w = data[0].length;
    var ms = 0;
    for(var k = 0; k < 4; k += 1){
      var ps = 1;
      var vi = i0+di[k];
      var vj = j0+dj[k];
      var t;
      for(var l = 0; l < n-1; l += 1){
        if(vi >= 0 && vi < h && vj >= 0 && vj < w){
          t = data[vi][vj];
          if(t === p){
            ps += 1;
          }else{
            break;
          }
        }else{
          break;
        }
        vi += di[k];
        vj += dj[k];
      }
      var vi = i0+di[k+4];
      var vj = j0+dj[k+4];
      var t;
      for(var l = 0; l < n-1; l += 1){
        if(vi >= 0 && vi < h && vj >= 0 && vj < w){
          t = data[vi][vj];
          if(t === p){
            ps += 1;
          }else{
            break;
          }
        }else{
          break;
        }
        vi += di[k+4];
        vj += dj[k+4];
      }

      if(ps > ms){
        ms = ps;
      }
    }

    var ls = 0;
    var ns = 0;
    for(var k = 0; k < 8; k += 1){
      var vi = i0+di[k];
      var vj = j0+dj[k];
      var t;
      if(vi >= 0 && vi < h && vj >= 0 && vj < w){
        t = data[vi][vj];
        if(t !== 0){
          ls += 1;
        }
        if(t === p || t === 0){
          ns += 1;
        }
      }
    }

    return [ms, ls, ns];
  }

  function eval_board(data, p, ht, deb){
    var h = data.length, w = data[0].length;
    var q = p === 1 ? 2 : 1;

    for(var i = 0; i < h; i += 1){
      var t;
      var tr = [];
      var sv = 0;
      for(var j = 0; j < w; j += 1){
        t = data[i][j];
        tr.push(t);
        if(t !== 0){
          sv += 1;
        }
      }
      if(sv >= 2){
        ptmch(tr, ht, p, q, deb);
      }
    }
    if(rule.kinsh === 0 && ht[p][0]+ht[p][1] >= 1){
      return 1;
    }
    if(rule.kinsh === 1){
      if(ht[p][6]+ht[p][7] >= 2){
        if(deb){
          console.log("33");
        }
        return 0;
      }
    }
    if(rule.kinsh === 2 && p === 1){
      if(ht[p][0] >= 1){
        if(deb){
          console.log("6");
        }
        return 0;
      }
      if(ht[p][1] >= 1){
        return 1;
      }
      if((ht[p][6]+ht[p][7] >= 2) || (ht[p][2]+ht[p][3]+ht[p][4]+ht[p][5] >= 2)){
        if(deb){
          console.log("33, 44");
        }
        return 0;
      }
    }
    for(var j = 0; j < w; j += 1){
      var t;
      var tr = [];
      var sv = 0;
      for(var i = 0; i < h; i += 1){
        t = data[i][j];
        tr.push(t);
        if(t !== 0){
          sv += 1;
        }
      }
      if(sv >= 2){
        ptmch(tr, ht, p, q, deb);
      }
    }
    if(rule.kinsh === 0 && ht[p][0]+ht[p][1] >= 1){
      return 1;
    }
    if(rule.kinsh === 1){
      if(ht[p][6]+ht[p][7] >= 2){
        if(deb){
          console.log("33");
        }
        return 0;
      }
    }
    if(rule.kinsh === 2 && p === 1){
      if(ht[p][0] >= 1){
        if(deb){
          console.log("6");
        }
        return 0;
      }
      if(ht[p][1] >= 1){
        return 1;
      }
      if((ht[p][6]+ht[p][7] >= 2) || (ht[p][2]+ht[p][3]+ht[p][4]+ht[p][5] >= 2)){
        if(deb){
          console.log("33, 44");
        }
        return 0;
      }
    }
    for(var j = -w+n, lj = w-n+1; j < lj; j += 1){
      var t;
      var tr = [];
      var sv = 0;
      for(var i = 0; i < h; i += 1){
        if(j+i < w && j+i >= 0){
          t = data[i][j+i];
          tr.push(t);
          if(t !== 0){
            sv += 1;
          }
        }
      }
      if(sv >= 2){
        ptmch(tr, ht, p, q, deb);
      }
    }
    if(rule.kinsh === 0 && ht[p][0]+ht[p][1] >= 1){
      return 1;
    }
    if(rule.kinsh === 1){
      if(ht[p][6]+ht[p][7] >= 2){
        if(deb){
          console.log("33");
        }
        return 0;
      }
    }
    if(rule.kinsh === 2 && p === 1){
      if(ht[p][0] >= 1){
        if(deb){
          console.log("6");
        }
        return 0;
      }
      if(ht[p][1] >= 1){
        return 1;
      }
      if((ht[p][6]+ht[p][7] >= 2) || (ht[p][2]+ht[p][3]+ht[p][4]+ht[p][5] >= 2)){
        if(deb){
          console.log("33, 44");
        }
        return 0;
      }
    }
    for(var j = 0, lj = w*2-n+1; j < lj; j += 1){
      var t;
      var tr = [];
      var sv = 0;
      for(var i = 0; i < h; i += 1){
        if(j-i < w && j-i >= 0){
          t = data[i][j-i];
          tr.push(t);
          if(t !== 0){
            sv += 1;
          }
        }
      }
      if(sv >= 2){
        ptmch(tr, ht, p, q, deb);
      }
    }
    if(rule.kinsh === 0 && ht[p][0]+ht[p][1] >= 1){
      return 1;
    }
    if(rule.kinsh === 1){
      if(ht[p][6]+ht[p][7] >= 2){
        if(deb){
          console.log("33");
        }
        return 0;
      }
    }
    if(rule.kinsh === 2 && p === 1){
      if(ht[p][0] >= 1){
        if(deb){
          console.log("6");
        }
        return 0;
      }
      if(ht[p][1] >= 1){
        return 1;
      }
      if((ht[p][6]+ht[p][7] >= 2) || (ht[p][2]+ht[p][3]+ht[p][4]+ht[p][5] >= 2)){
        if(deb){
          console.log("33, 44");
        }
        return 0;
      }
    }
    return 1;
  }

  function countup(ht, score, p, q){
    var def = [
      {name:"六連",     sc:score_limit, d:1}, //0
      {name:"五連",     sc:score_limit, d:1}, //1
      {name:"棒四",     sc:256, d:8},  //2
      {name:"トビ棒四", sc:128, d:8}, //3
      {name:"活四",     sc:64, d:8}, //4
      {name:"トビ四",   sc:64, d:8}, //5
      {name:"活三",     sc:16, d:2}, //6
      {name:"トビ活三", sc:16, d:2}, //7
      {name:"",     sc:1, d:1} //8
    ];
    var s1 = 0;
    var s2 = 0;
    for(var i = 0, li = def.length; i < li; i += 1){
      s1 += def[i].sc*ht[p][i];
      s2 += def[i].sc*ht[q][i];//*def[i].d;
    }
    score[p] = s1;
    score[q] = s2;
    if((rule.kinsh === 0 ||(rule.kinsh === 2 && p === 2))){
      if((ht[p][2]+ht[p][3]+ht[p][4]+ht[p][5]+ht[p][6]+ht[p][7]>= 2)){
        score[p] *= 4;
      }
      if((ht[q][2]+ht[q][3]+ht[q][4]+ht[q][5]+ht[q][6]+ht[q][7]>= 2)){
        score[q] *= 4;
      }
    }else{
      if((ht[p][2]+ht[p][3]+ht[p][4]+ht[p][5]>= 1) && (ht[p][6]+ht[p][7]>= 1)){
        score[p] *= 4;
      }
      if((ht[q][2]+ht[q][3]+ht[q][4]+ht[q][5]>= 1) && (ht[q][6]+ht[q][7]>= 1)){
        score[q] *= 4;
      }
    }
    if(score[p] > score_limit){
      score[p] = score_limit;
    }
    if(score[q] > score_limit){
      score[q] = score_limit;
    }
  }

  function ptmch(tr, ht, p, q, deb){
    var def = [
      {v:6,vp:1,z:0,zp:0,l:6,name:"六連"},  //12
      {v:5,vp:1,z:0,zp:0,l:5,name:"五連"}, //10
      {v:4,vp:1,z:2,zp:2,l:6,name:"棒四"},  //8
      {v:4,vp:2,z:3,zp:3,l:7,name:"トビ棒四"}, //8
      {v:4,vp:1,z:1,zp:1,l:5,name:"活四"}, //8
      {v:4,vp:2,z:2,zp:2,l:6,name:"トビ四"}, //8
      {v:3,vp:1,z:2,zp:2,l:5,name:"活三"}, //6
      {v:3,vp:2,z:3,zp:3,l:6,name:"トビ活三"}, //6
      {v:2,vp:1,z:3,zp:2,l:5,name:""} //4
    ];
    var s5_1 = false;
    var li = def.length;
    for(var i = 0; i < li && !s5_1; i += 1){
      var d = def[i];
      for(var j = 0, lj = tr.length-d.l+1; j < lj && !s5_1; j += 1){
        var t1 = -1;
        var ti1 = 0;
        var tvi = -1;
        var v = 0;
        var z = 0;
        var vp = 0;
        var zp = 0;
        for(var k = 0, lk = d.l; k < lk; k += 1){
          var c = tr[j+k];
          if(c === 0){
            if(c !== t1){
              zp += 1;
            }
            z += 1;
          }else if(c === tvi){
            if(c !== t1){
              vp += 1;
            }
            v += 1;
            ti1 = k;
          }else if(tvi === -1 && ((p != null && c === p) || (q != null && c === q))){
            tvi = c;
            vp += 1;
            v += 1;
          }else{
            j = j+ti1;
            v = 0;
            break;
          }
          t1 = c;
        }
        if(v === d.v){
          if(tvi === p && vp === d.vp && z === d.z && zp === d.zp){
            ht[p][i] += 1;
            if(deb){
              console.log(d.name, p);
            }
            if(i === 1){
              s5_1 = true;
            }
          }else if(tvi === q && vp === d.vp && z === d.z && zp === d.zp){
            ht[q][i] += 1;
            if(deb){
              console.log(d.name, q);
            }
            if(i === 1){
              s5_1 = true;
            }
          }
        }
      }
    }
  }

  function dcopy(data){
    var data2 = new Array(data.length);
    for(var i = 0, li = data.length; i < li; i += 1){
      data2[i] = new Array(li);
      for(var j = 0, lj = data.length; j < lj; j += 1){
        data2[i][j] = data[i][j];
      }
    }
    return data2;
  }

  function put(i, j){
    lastput.push([i,j,turn]);
    data[i][j] = turn;
  }

  function undo(){
    if(timer !== -1){
      return;
    }
    var ijt;
    while(lastput.length>0){
      ijt = lastput.pop();
      data[ijt[0]][ijt[1]] = 0;
      turn = ijt[2];
      if(user[ijt[2]]){
        break;
      }
    }
    g_state = [1,"play",""];
    render();
  }

  function get_lastput(){
    var ijt;
    if(lastput.length > 0){
      ijt = lastput[lastput.length-1];
      return ijt;
    }
  }

  function convert_to_char(n){
    if(n === 1){
      return "<div class='black'></div>";
    }else if(n === 2){
      return "<div class='white'></div>"
    }else{
      return "";
    }
  }

  function to_center(ele){
    ele.dataset.blink = "0";
    ele.dataset.center = "-1";
    setTimeout(function(){
      ele.dataset.center = "0";
      setTimeout(function(){
        ele.dataset.center = "-1";
      },3500);
    },10);
  }

  function blink(ele){
    ele.dataset.center = "-1";
    setTimeout(function(){
      ele.dataset.center = "0";
      var i = 10;
      timer = setInterval(function(){
        if(ele.dataset.blink === "1"){
          ele.dataset.blink = "0";
        }else{
          ele.dataset.blink = "1";
          if(uwin){
            ele.dataset.bcolor = "win";
          }else{
            ele.dataset.bcolor = "";
          }
        }
        i -= 1;
        if(i <= 0){
          ele.dataset.blink = "0";
          ele.dataset.bcolor = "";
          ele.dataset.center = "-1";
          clearInterval(timer);
          timer = -1;
        }
      },300);      
    },100);

  }

  function render(put){
    if(g_state[1] !== "ini" && g_state[1] !== "set"){
      for(var  i = 0; i < data.length; i += 1){
        for(var j = 0; j < data[i].length; j += 1){
          table.rows[i].cells[j].innerHTML = convert_to_char(data[i][j]);
          table.rows[i].cells[j].dataset.lastput = 0;
          if(history){
            table.rows[i].cells[j].dataset.history = "1";
          }else{
            table.rows[i].cells[j].dataset.history = "0";
          }
        }
      }
      if(put){
        var ijt = get_lastput();
        if(ijt != null){
          table.rows[ijt[0]].cells[ijt[1]].dataset.lastput = "-1";
          setTimeout(function(){
            table.rows[ijt[0]].cells[ijt[1]].dataset.lastput = "1";
          },30);
          d_ctrl.style.display = "inline-block";
        }else{
          d_ctrl.style.display = "none";
        }
        msg.dataset.center = "-1";
      }
      for(var i = 0; i < lastput.length; i += 1){
        table.rows[lastput[i][0]].cells[lastput[i][1]].firstChild.innerHTML = "<span>"+(i+1)+"</span>";
      }
    }

    if(g_state[0]){
      if(g_state[1] === "start"){
        table.onclick = tbl_click;
        d_stop.style.display = "block";
        d_start.style.display = "none";
        d_set.style.display = "none";
        to_center(msg);
      }else if(g_state[1] === "play"){
        table.onclick = tbl_click;
        d_stop.style.display = "block";
        d_start.style.display = "none";
        d_set.style.display = "none";
        msg.dataset.center = "-1";
      }else if(g_state[1] === "stop"){
        d_ctrl.style.display = "none";
        table.onclick = null;
        d_stop.style.display = "none";
        d_start.style.display = "block";
        d_set.style.display = "none";
        to_center(msg);
      }else if(g_state[1] === "set"){
        d_ctrl.style.display = "none";
        table.onclick = null;
        d_stop.style.display = "none";
        d_start.style.display = "none";
        d_set.style.display = "none";
        to_center(msg);
      }else if(g_state[1] === "ini"){
        d_ctrl.style.display = "none";
        table.onclick = null;
        d_stop.style.display = "none";
        d_start.style.display = "block";
        d_set.style.display = "none";
        to_center(msg);
      }else if(g_state[1] === "game_over"){
        d_ctrl.style.display = "inline-block";
        table.onclick = null;
        d_stop.style.display = "none";
        d_start.style.display = "block";
        d_set.style.display = "none";
        blink(msg);
      }
      msg.innerHTML = "<span>"+g_state[2]+"</span>";
      g_state[0] = 0;
    }
  }

  // 初期化
  init();

  function count_data(data){
    var s = 0;
    for(var i = 0; i < data.length; i += 1){
      for(var j = 0; j < data[i].length; j += 1){
        if(data[i][j] > 0){
          s += 1;
        }
      }
    }
    return s;
  }

  function tweet(){
    var str = "";
    var s = count_data(data);
    var ht = [null,[0,0,0,0,0, 0,0,0,0,0],[0,0,0,0,0, 0,0,0,0,0]];
    eval_board(data, 1, ht, deb);
    var arr_score = [null, 0, 0];
    countup(ht, arr_score, 1, 2);
    if(arr_score[1] >= score_limit && user[1] > 0){
      str=""+s+"手目 あなたの勝ちです。";
    }else if(arr_score[1] >= score_limit && com[1] > 0){
      str=""+s+"手目 コンピューターの勝ちです。”";
    }else if(arr_score[2] >= score_limit && user[2] > 0){
      str=""+s+"手目 あなたの勝ちです。";
    }else if(arr_score[2] >= score_limit && com[2] > 0){
      str=""+s+"手目 コンピューターの勝ちです。”";
    }else if(s === size_h*size_w){
      str=""+s+"手目 引き分け。”";
    }else{
      str=""+s+"手目 対戦しています。”";
    }
    window.open("https://twitter.com/intent/tweet?url="
    +encodeURIComponent(location.href)
    +"&text="+encodeURIComponent(str),"tweet", "top=20,left=20,width=400,height=500");
  }
}
css/style.css
html {background-color:#eeffee; overflow-y: scroll;}
body {
    margin: auto; max-width: 100%; width: 800px; text-align:center;
}
.app * {
    font-family: 'メイリオ', Meiryo,'ヒラギノ角ゴシック','Hiragino Sans',sans-serif;
    font-size: 20px;
    line-height: 1.5em;
}

body>div {
    display:inline-block; text-align:left;
}

.app {
    display:none;
    position: relative;
    min-height:1000px;
}

.app .main_disp {
    position: relative;
    margin: 0em;
}

.app p {
    max-width: 320px;
}

.app .outer_msg {
    width: 100%;
    max-width: 100%;
    text-align: center;
    position: relative;
}

.app .msg {
    display: inline-block;
    position: relative;
    min-height: 2em;
    padding: 0.5em;
    text-align: center;
    box-sizing: border-box;
    cursor: pointer;
}

.app .msg[data-center="-1"]{
    top: -1000px;
    opacity: 0;
    transition: none;
}

.app .msg[data-center="0"]{
    transition: all 500ms ease-out;
    top: -420px;
    z-index: 200;
    background: rgba(255,255,255,0.8);
}

.app .msg[data-center="1"]{
    transition: none;
    top: 0px;
}

.app .msg span {
    transition: all 80ms linear;
}

.app [data-blink="1"][data-bcolor="win"] span {
    border: 3px dotted rgb(255, 185, 33);
    color:rgb(218, 25, 0);
    text-shadow: 0px 0px 4px rgb(0, 255, 13);
}

.app [data-blink="1"] span {
    border: 3px dotted rgb(255, 104, 129);
}

.app .ctrl_iblock {
    display: inline-block;
}

input[type="button"] {
    position: relative;
    font-size: 26px;
    height: 52px;
}

.d_board {
    display: inline-block;
    margin-top: 0.25em;
}

.d_board table {
    border-collapse: collapse; border: 1px solid #000000;  background-color: #deb887;
}

.d_board table td {
  min-width: 45px; min-height: 45px; width: 45px; height: 45px; text-align:center;
  padding:0px; position: relative;
  cursor: pointer;
}

.d_board table td[data-bg="00"] {
    background: url("images/bg00.png");
}

.d_board table td[data-bg="01"] {
    background: url("images/bg01.png");
}

.d_board table td[data-bg="10"] {
    background: url("images/bg10.png");
}

.d_board table td[data-bg="11"] {
    background: url("images/bg11.png");
}

.d_board table td[data-bg="1x"] {
    background: url("images/bg1x.png");
}
.d_board table td[data-bg="0x"] {
    background: url("images/bg0x.png");
}
.d_board table td[data-bg="x1"] {
    background: url("images/bgx1.png");
}
.d_board table td[data-bg="x0"] {
    background: url("images/bgx0.png");
}

.d_board table td[data-bg="xx"] {
    background: url("images/bgxx.png");
}

.d_board table td[data-bg="xx1"] {
    background: url("images/bgxx1.png");
}


.d_board table td[data-lastput="-1"] {
    box-shadow: 0px 0px 1px 1px #ffff00 inset;
}

.d_board table td[data-lastput="1"] {
    box-shadow: 0px 0px 1px 1px #ffff00 inset;
}


.d_board table td:hover {
    box-shadow: 0px 0px 1px 1px #ffffff inset;
}

.d_board table .black {
    border-radius: 50%; width: 90%; height: 90%; display: inline-block;position: absolute; top:5%; left:5%;
    background: rgb(39, 39, 39);
    background: radial-gradient(circle at 40% 40%, rgb(114, 114, 114) 0%, rgba(0,0,0,1) 50%, rgba(0,0,0,1) 100%);
    box-shadow: 2px 2px 3px rgb(0,0,0);
    transition: all 100ms ease-in;
    transform-origin: 50%;
}

.d_board table .white {
    border-radius: 50%; width: 90%; height: 90%; display: inline-block; position: absolute; top:5%; left:5%;
    background: rgb(255,255,255);
    background: radial-gradient(circle at 40% 40%, rgb(255, 255, 255) 0%, rgb(245, 245, 245) 50%, rgb(90, 90, 90) 100%);
    box-shadow: 2px 2px 3px rgb(116, 116, 116);
    transition: all 100ms ease-in;
    transform-origin: 50%;
}

.d_board table td .black span {
    font-size: 18px;
    display:block;
    margin: 15% 0%;
    opacity:0;
}

.d_board table td .white span {
    font-size: 18px;
    display:block;
    margin: 15% 0%;
    opacity:0;
}

.d_board table td[data-history="1"] .black span {
    color: rgb(255,255,255);
    opacity:1;
}

.d_board table td[data-history="1"] .white span {
    color: rgb(0, 0, 0);
    opacity:1;
}

.d_board table td[data-lastput="-1"] .black {
    transform: translateY(-60px);
    z-index:100;
}

.d_board table td[data-lastput="-1"] .white {
    transform: translateY(-60px);
    z-index:100;
}

盤面画像

bg00.png
bg0x.png
bg01.png
bg1x.png
bg10.png
bg11.png
bgx0.png
bgx1.png
bgxx.png
bgxx1.png

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

日本じゃいまいち有名じゃないけど、Googleがメイン?で使ってるという、AngulartDartでJSアプリ作ってみた

有名なGoogle製フレームワークAngular。
https://angular.jp

そのAngularから最近別れたDart版フレームワークAngularDart
https://angulardart.dev

こっちでDart使ったので、
https://qiita.com/mharadazz/items/6fa0604789041b2a3509

以下のアプリの一部をwebViewで実現して、工数削減しようとして使ってみた。2つ作るのはめんどいからね。
https://play.google.com/store/apps/details?id=jp.co.projapan.solitaire
https://apps.apple.com/jp/app/ソリティアv/id341532096

良かった点

  • ビルドが速い。5秒以外。ビルドが速いのは正義。
  • Dartいい。StageXLアプリでも使ったけど、いい。
  • 既存タグ内にif文書けるので、タグが深くならない。
  • そんなに難しいことがないのでとっつきやすい。
  • htmlテンプレート内でも補完してくれる。無い変数参照すると、警告出してくれる。素敵。

悪かった点

  • 時々ビルドできなくなる。Dartバージョンアップしたり、フレームワークバージョン変えたり、色々変えて回避する。:joy:
  • 時々謎の呪文を書かないといけない。
import 'products/root_component.template.dart' as root_temp;

そんなソース無いがね・・・。

  • 謎のキーワードng。まあaNGularのNGなんだろう。
  • 情報少なーい。突っ込んだ情報はほんとにない。ハマると辛い:cold_sweat:

全体的に生産性高い。いい。素敵:heart_eyes:

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

人気の天気APIをまとめてみた

気象データを使用してアプリケーションを構築する場合は、お役にたてる情報が提供できるかと思います。 天気情報は、常に消費者と企業の両方にとって重要なサービスです。

GoogleやYahooなどの検索エンジンは、天気予報を中核として提供します。 航空会社は、天気予報を使用して乱気流を予測します。 保険会社は保険数理上のリスクを測定するために気象データを使用します。農業部門は気象データを使用して作物の収穫量を測定します。 リアルタイムで正確な天気予報には価値があります!

今日の主要なハイテク企業は、天候のわずかな変化でも感知するための技術開発に投資しています。 IBMが2016年に20億ドル以上でThe Weather Companyを買収したと伝えられている理由です。
Weather_1.png

Weather APIは、温度、降水量、湿度だけでなく、UVインデックスと汚染レベルを超える詳細な情報を提供します。 時系列データを使用すると、傾向やパターンを発見し、気象条件に基づいてイベントを計画できます。

また、これらのAPIは、グローバルな規模でアラートと通知のメカニズムが組み込まれているものもあるため、災害管理に役立つリソースになる可能性があります。

オプションをナビゲートしやすくするために、さまざまなWeather APIを比較し、異なるユースケースと価格設定を共有し、Rakuten RapidAPIマーケットプレイスでそれらを見つけるのに役立つ様、この記事を作成しました。

素晴らしいアプリの構築を開始する準備はできましたか?

こちらがRakuten RapidAPIの人気のトップ10天気APIです:

OpenWeatherMap API
Dark Sky API
Weather API
Simple Weather API
Weather2020 API
Air Quality API
Weather by Location API
Visual Crossing Weather API
AccuWeather API
AirVisual API

1.OpenWeatherMap API

Weather_2.png

OpenWeatherMap APIは、ビッグデータ、データ処理、衛星画像処理のエンジニアおよび専門家グループによって2014年に設立されました。 APIは、現在の天気、過去の天気データ、および予報の詳細を返します。世界中の40,000以上の場所からデータを収集し、必要な場所に関する正確な天気情報を取得できるようにします。

以下のAPIの詳細をご覧ください。

Rakuten RapidAPIのAPIドキュメント
•サービスプロバイダーのWebサイトのドキュメント:OpenWeatherMap APIドキュメント
•価格の詳細:OpenWeatherMapの価格の詳細

長所

•シンプルで高速なAP​​I
•複数の都市の最新の天気情報と最大16日間の天気予報を提供します。また、5日と3時間先の予測を取得できます
•分析に役立つ統計、グラフィック、および今日の天気図を提供します
•選択した場所の風、気圧、降水量、雲を表示するインタラクティブなマップを提供します
•データは、JSON、XML、HTMLなど、さまざまな形式で提供されます

短所

•無料利用枠の機能は制限されており、1日に要求できるのは100件までです。ニーズがこれを超える場合は、その有料プランのいずれかに加入する必要があります

ユースケース

•農家が現在の天気と将来の天気予報を知るのに役立つアプリケーションを作成します
•気温、湿度、風速、その他の重要な気象パラメーターなどの気象条件を評価するためのアプリケーションを作成します
•OpenWeatherMapサービスにリンクする個人的な気象観測所を作成します
•過去5年間の都市の履歴データを取得します、これは、1時間ごとの一括抽出として提供されます

2.Dark Sky API

Weather_3.png

Dark Sky APIは、世界中のあらゆる場所に関する包括的な気象情報を提供する堅牢なAPIです。
このAPIは、現在の気象条件、次の1時間の1分ごとの予測、次の48時間の1時間ごとの予測、および来週。

以下のAPIの詳細をご覧ください。

Rakuten RapidAPIのAPIドキュメント
•サービスプロバイダーのWebサイトのドキュメント:Darksky APIドキュメント
•価格の詳細:Darksky APIの価格の詳細

長所

•ハイパーローカルな気象情報の正確なソース
•天気データは頻繁に更新されます。たとえば、分ごとの予測データは5分ごとに更新され、時間ごとおよび日ごとの予測データは1時間ごとに更新されます
•過去数十年に遡る過去の気象データが利用可能
•39の異なる言語をサポート

短所

•厳選された一部の国でのみ利用可能な厳しい気象警報
•無料利用枠では、1か月あたり100件までのリクエストが許可されます。その後、追加の各リクエストの価格は0.001ドルです

ユースケース

•現在および予測される気象条件を表示するためのアプリケーションの構築
•分析目的および科学研究に使用できる履歴データの取得
•気温、風、およびその他の気象データを取得します。これは、沿岸地域や山岳地域など、さまざまな場所でのルート計画に役立ちます

3.Weather API

Weather_4.png
Weather APIを使用すると、超高速データにアクセスして、最新の気象情報に関するすべてのニーズを満たすことができます。 APIを使用すると、世界中のあらゆる場所に関する正確な過去、現在、および予測の気象データを取得できます。

以下のAPIの詳細をご覧ください。

Rakuten RapidAPIのAPIドキュメント
•サービスプロバイダーのWebサイトのドキュメント:Weatherbit.io API
•価格の詳細:Weather APIの価格の詳細

長所

•世界中の45,000を超えるライブ気象観測所からの高速で最新の気象情報
•120,000以上のステーションからの過去10年間の包括的な履歴データ
•緯度/経度、都市名、IPアドレスなど、気象データを検索するいくつかの方法

短所

•予測は16日間に制限されているため、長期的な予測を探している人には不便です
•1日あたり150を超えないAPIリクエストの制限付き無料割り当て
•限られた場所でのみ利用可能な厳しい気象警報

ユースケース

•現在の気象条件の検出
•モバイルアプリケーションの開発
•今後16日間までの気象条件の予測

4.Simple Weather API

Weather_5.png
Simple Weather APIは、世界中の複数の場所の現在の天気を取得するための使いやすいAPIです。 APIにより、天気情報は誰でも簡単にアクセスできるようになりました。

以下のAPIの詳細をご覧ください。

Rakuten RapidAPIのAPIドキュメント
•価格の詳細:無料

長所

•使いやすい
•無料で利用できる
•現在の気象条件をアプリケーションに統合するためのシンプルなツール
•温度や気象条件の説明など、さまざまな気象パラメーターを提供

短所

•シンプルなツールであり、特に包括的なデータを探している人にとっては、一部の詳細を見落とす可能性があります

ユースケース

•現在の気象条件をアプリケーションに簡単に統合
•場所の温度を取得する
•天気予報の取得

5.Weather2020 API

Weather_6.png

Weather2020 APIは、人気のある長期天気予報APIで、最大12週間の包括的な予報を提供します。
有名な気象学者ゲイリー・レザックによって開発されました。 APIは精度を高めるために、LRCとして知られる強力な長距離気象モデルテクノロジーを使用します。

以下のAPIの詳細をご覧ください。

Rakuten RapidAPIのAPIドキュメント
•サービスプロバイダーのWebサイトのドキュメント:Weather2020ドキュメント
•価格の詳細:Weather2020 APIの価格の詳細

長所

•企業や消費者向けに正確で長期的な予測を作成できます
•1Weatherなどのいくつかの一般的なモバイルアプリケーションで既に使用されています、さらに、1日に1,000万件以上の予測を提供します
•主要な気象指標、傾向、および重要な更新を含むインタラクティブなダッシュボードと分析を提供します

短所

•無料利用枠の上限は1日あたり1,000リクエスト
•有料の顧客のみが利用できるビデオ予測

ユースケース

•気象条件データをモバイルアプリとWebサイトに簡単に統合
•場所の天気予報に関する教育ビデオを紹介する
•旅行、屋外、またはその他のアプリケーションに有用な天気情報を追加する

6.Air Quality API

Weather_7.png

Air Quality APIは、世界のあらゆる場所の包括的な現在、予測、および過去の大気質データを提供します。
場所の緯度と経度を提供した後、APIは信頼できる大気品質情報を返します。この情報は、旅行の手配など、幅広い目的に使用できます。 Weatherbit Air Quality APIに基づいています。

以下のAPIの詳細をご覧ください。

Rakuten RapidAPIのAPIドキュメント
•サービスプロバイダーのWebサイトのドキュメント:Weatherbit.io API
•価格の詳細:Air Quality APIの価格の詳細

長所

•世界最高の気象モデルと社内技術に基づいた、非常に正確な現在の大気質データ
•彼らの技術は、長年の経験と献身的な研究を続ける研究気象学者によって補完されています。
•3日間(時間単位)の大気質予測を提供します
•また、大気質指標スコア(AQI)も返します。
•また、主要な環境汚染物質に関するデータを返します

短所

•過去の大気質は24時間に制限されています。これは、広範な過去のデータを探している人には適さない可能性があります
•無料の割り当てでは、1日あたり25を超えるAPIリクエストは許可されません。

ユースケース

•ルート計画および旅行手配のための大気質データの取得
•建設活動の環境への影響を最小限に抑えるための大気質データの取得
•環境に対する表面汚染物質の影響を防止するための警告の取得

7.Weather by Location API

Weather_8.png

Weather by Location APIは使いやすいAPIで、IPアドレス、郵便番号、経度/緯度座標によって天気情報を取得できます。

以下のAPIの詳細をご覧ください。

Rakuten RapidAPIのAPIドキュメント
•価格の詳細:Location APIの価格の詳細による天気

長所

•すばやく簡単に使用できます
•温度、圧力、風速、湿度など、幅広い気象条件を提供します
•IPアドレス、郵便番号、経度/緯度を指定して、天気情報を取得できます

短所

•無料利用枠の上限は1日あたり100リクエストです。 使用をさらに延長するには、その有料プランのいずれかを使用する必要があります。

ユースケース

•指定されたジオロケーションの現在の観測気象データの取得
•旅行、スポーツ、その他の活動を計画するためにさまざまな気象条件を取得する
•便利な天気情報をモバイルアプリケーションとWebサイトに統合する

8.Visual Crossing Weather API

Weather_9.png

Visual Crossing Weather APIは、グローバルな天気予報データへの即時かつスケーラブルなアクセスと、Webサイト、企業、旅行業界へのさまざまな天気レポートを提供します。

以下のAPIの詳細をご覧ください。

Rakuten RapidAPIのAPIドキュメント
•サービスプロバイダーのWebサイトのドキュメント:Visual Crossing Weather API
•価格の詳細:Visual Crossing Weather APIの価格の詳細

長所

•シンプルで使いやすいAPI
•最大7日間の正確な天気予報データを提供します
•数十年にわたる包括的な時間ごとおよび日ごとの過去の気象データを提供します
•データは、便利なコンマ区切りの表形式で提供されます

短所

•無料利用枠の上限は1か月あたり500件
•優れた機能を備えたAPIの価格プランは、要件の小さいお客様にはコストがかかる可能性があります

ユースケース

•正確な気象および気候情報をアプリケーションおよびバックエンドシステムに統合する
•気象データをWebサイトやその他のWeb開発プロジェクトに統合します

9.AccuWeather API

Weather_10.png

AccuWeather APIは、地球上のあらゆる場所に関する正確な気象データを提供します。 APIを使用すると、幅広いプラットフォームに展開できる魅力的なビデオプレゼンテーションを使用して、1分ごとの予測を取得できます。

以下のAPIの詳細をご覧ください。

RakutenRapidAPIのAPIドキュメント
•サービスプロバイダーのWebサイトのドキュメント:AccuWeather APIドキュメント
•価格の詳細:AccuWeather APIの価格の詳細

長所

•非常に正確な過去、現在、および予報の気象情報を提供します
•それに応じて計画を立てるために使用できる厳しい気象警報を提供します
•APIの機能をテストするための無料バージョンがあります
•カスタマイズされた天気情報コンテンツを提供します

短所

•最小限の予算でプレミアムプランを開発者に提供できない場合があります

ユースケース

•魅力的なビデオ天気プレゼンテーションをアプリケーションに統合する
•気象警報や暴風雨警報などの運用リスクの管理

10.AirVisual API

Weather_11.png
AirVisual APIは、世界中のほとんどの場所で空気の質を評価できるシンプルなAPIです。このAPIは、世界中の8,000を超える地上センサーからデータを収集し、目に見えない脅威に対する監視とアクションの実行を可能にします。

以下のAPIの詳細をご覧ください。

Rakuten RapidAPIのAPIドキュメント
•サービスプロバイダーのWebサイトのドキュメント:AirVisual APIドキュメント

長所

•APIを試すための無料版があります
•世界中の場所の信頼できる過去、リアルタイム、および予測の大気質データを提供します
•世界的な大気汚染問題に対するソリューションを提供します
•機械学習テクノロジーを使用してすべてのデータポイントを検証し、正確性と適時性を確保します

短所

•世界の一部の国はカバーされていません

ユースケース

•大気汚染問題に対処するための都市の大気質に関するデータの取得
•都市計画のための汚染物質濃度、温度、湿度に関するデータの取得
•地域の大気質と気象データを監視するアプリケーションの構築

これがRakuten RapidAPIのベスト天気APIのトップ10リストです。このリソースがお役に立てば幸いです!

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

Riot.js v4 サーバーサイドレンダリング(SSR)

Riot.js Advent Calendar 2019 の19日目です。

SSR

スーパースペシャルレアだったり、ダブルスーパーレアではありません。
サーバーサイドレンダリングです。

使うかどうかは賛否両論ありますが、使えるようにはなっておいた方が良いので勉強します。

@riotjs/ssr

Riot.jsにもSSRの為のモジュールがあります。
@riotjs/ssr
インストールはnpm i -S @riotjs/ssr

情報量が少ない、というか全く見つからなかったので結構苦戦しました。
Readme.mdによると@riotjs/ssrに用意されているのは、以下の3種類。

render

import MyComponent from './my-component.riot'
import render from '@riotjs/ssr'

const html = render('my-component', MyComponent, { some: 'initial props' })

レンダリングした結果(HTML)を返します。

fragments

import MyComponent from './my-component.riot'
import {fragments} from '@riotjs/ssr'

const {html, css} = fragments('my-component', MyComponent, { some: 'initial props' })

レンダリングした結果からHTMLとCSSを分離して取得します。
というか、実際はレンダリング結果をそのまま返している。(HTML側が減らされている)

fragments
export const fragments = curry(createRenderer)(frags => frags)
default
export default curry(createRenderer)(({html}) => html)

register

import register from '@riotjs/ssr/register'

import MyComponent from './my-component.riot' // It will fail

// from now on you can load `.riot` tags in node
const unregister = register()

import MyComponent from './my-component.riot' // it works!

// normally you will not need to call this function but if you want you can unhook the riot loader
unregister()

Node.jsでRiotファイルを扱えるようにします。
以下のようにすることで別の拡張子も対応可能。

register({ exts: ['.riot', '.tag'] })

piratesを使ってrequireをジャックしています。
@riotjs/compilerを使ってcompileした結果を@babel/coreのtransformに食わせているだけです。
https://github.com/riot/ssr/blob/master/src/register.js

使ってみる

今回はWeb Serverにはexpressを使っています。
Riot.js v4 自分でRouterを作るで作ったサンプルと同じ様なことをSSRで実装します。

index.js
import { fragments } from '@riotjs/ssr';
import register from '@riotjs/ssr/register';
import express from 'express';

// 拡張子.riotを登録
register();

// Express Server
const app = express();
app.listen(4500);

// Router and SSR
const ssr = (file, obj) => {
  try {
    // Riotファイルをロード
    const tag = require(`./${ file || 'home' }.riot`).default;
    // レンダリング実施
    return fragments(tag.name, tag, { "params": obj });
  } catch (ex) {
    // エラーが発生した場合はエラーページへ
    return file !== 'notfound' ? ssr('notfound', ex) : {};
  }
};

// favicon.icoは無視
app.get('/favicon.ico', (req, res) => res.status(404).send(''));
// 全てのURLにマッチ
app.get('/*', (req, res) => {
  // 最初のパスはRiotファイル名とする
  let [ file, ...params ] = req.params['0'].split('/');

  // レンダリングを実施し、HTMLとCSSを取得
  const { html = '', css = ''} = ssr(file, params);

  // レンダリング結果を返す
  res.send(`<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>${ file || "Riot App" }</title>
  <style>${ css }</style>
</head>
<body>
${ html }
</body>
</html>`);
});
home.riot
<my-home>
  <nav>
    <ul>
      <li><a href="/hello">Hello</a></li>
      <li><a href="/goodbye">Goodbye</a></li>
      <li><a href="/greeting/hola">Hola</a></li>
      <li><a href="/greeting/adios">Adios</a></li>
      <li><a href="/hoge">hoge</a></li>
      <li><a href="/app">My APP</a></li>
      <li><a href="https://qiita.com/advent-calendar/2019/riotjs">通常リンク</a></li>
    </ul>
  </nav>
</my-home>
hello.riot
<my-hello>
  <p>Hello World!!</p>
</my-hello>
goodbye.riot
<my-goodbye>
  <p>Goodbye World!!</p>
  <style>
    p { color: red; }
  </style>
</my-goodbye>
greeting.riot
<my-message>
  <p>{ props.params[0].slice(0, 1).toUpperCase() }{ props.params[0].slice(1) } World!!</p>
</my-message>
notfound.riot
<my-notfound>
  <h3>Oops!!</h3>
  <h4>{ props.params.message }</h4>
  <pre>{ props.params.stack }</pre>
</my-notfound>
app.riot
<my-app>
  <p>{ state.message } World!!</p>
  <input type="button" value="Hello" onclick="{ greeting }">
  <input type="button" value="Goodbye" onclick="{ greeting }">
  <input type="button" value="Hola" onclick="{ greeting }">
  <input type="button" value="Adios" onclick="{ greeting }">
  <script>
    export default {
      state : {
        message : ""
      },

      onMounted(props, state) {
        state.message = "The";
        this.update();
      },

      greeting(e) {
        this.state.message = e.target.value;
        this.update();
      },
    }
  </script>
</my-app>

サーバー側なのでWebpackは使っていません。

package.json
{
  "name": "riotv4-ssr-sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon --ext js,riot --exec babel-node src/scripts/index.js"
  },
  "keywords": [],
  "author": "KAJIKEN <kentaro@kajiken.jp> (http://kajiken.jp)",
  "license": "MIT",
  "dependencies": {
    "@riotjs/compiler": "^4.5.4",
    "@riotjs/ssr": "^4.1.1",
    "express": "^4.17.1",
    "riot": "^4.7.2"
  },
  "devDependencies": {
    "@babel/core": "^7.7.5",
    "@babel/node": "^7.7.4",
    "@babel/preset-env": "^7.7.6",
    "nodemon": "^2.0.2"
  }
}
.babelrc
{
  "presets": [
    "@babel/preset-env"
  ]
}

動作確認とまとめ

a_2019_12_18_221131(slt)(raw).gif

サーバーサイドでレンダリングしている為、onMountedonBeforeMountなどマウント時に動くJavaScript以外は効果ありません。(消えます)
マウント時に動いたJavaScriptは実行した結果がHTMLに反映された状態でクライアントに返ります。HTMLに変化のないものは効果ありません。

その代わりサーバーサイドレンダリングのため、curlでももちろん結果は取れます。(検索エンジンBOTに強い)
b_2019_12_18_222309(slt)(raw).gif

そして表示処理と転送サイズは小さいので、レガシー端末やモバイル端末には良いかもしれません。

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

Vueで背景をページ毎に動的に変えたい

やる事

「画像を投稿して、投稿した画像にコメントを付けたい」
「コメントページは別ページ」
「別ページの背景を投稿画像にしたい。」
できるだけ頭を使わずにVue.js/Nuxt.jsでやっていきましょう。

方法

ページ内の要素をdivで包む

<template>
  <div>
    <!-- ここに中身。カニ味噌とか -->
  </div>
</template>

div要素のスタイルをバインド

バインド自体はsrcなどの属性に対して行うのとまったく同じ。

<template>
  <div v-bind:style="pageStyle">
    <!-- ここに中身。ウニとか -->
  </div>
</template>

スクリプトで背景要素を設定する。

連想配列にスタイルを書いていく。CSSのプロパティは通常ケバブケースだが、ここではキャメルケースで書くこと。
あと"url('ebikaniuni')"の中にURLを入れる部分に注意。落ち着いて書きましょう。
ちなみにTemplate部に直で書いてもOKだったりする。とても読みにくくなるけど...。

<script>
export default {
  data() {
    return {
      pageStyle: {
        // 背景変更用スタイル
        backgroundImage: "", // 背景読み込み前なので空白
        backgroundRepeat: "no-repeat",
        backgroundAttachment: "fixed",
        backgroundPosition: "center center",
      }
    };
 },
  created() {
    this.pageStyle.backgroundImage = "url('" + gazouNoUrl + "')"; 
    // クォートをいい感じにエスケープする必要がある点に注意。
  },
}

</script>

もっといい方法があったら教えてクレメンス

参考資料

思考の葉:v-bindでstyle、classの属性値を変える[Vue.js]

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

最近噂のDart言語でjsアプリ作ってみた

最近噂の3倍生産性が良いFlutterにつられて色々調べていたら、dart言語なるものがあり、JSアプリ作ろうかなーって思っていたので、使ってみた。
https://dart.dev

facebookインスタントゲームとして、公開済み。
https://fb.gg/play/solitaireproworld

javaで開発したandroidアプリを移植した。
https://play.google.com/store/apps/details?id=jp.co.projapan.solitaire

ゲームアプリなので、StageXLフレームワークを使用。これはFlash風APIになってる。
http://www.stagexl.org

良かった点

  • ビルドが速い。5秒程度でリロードされる。慣れるとサクサク開発できて、超気持ちいい。
  • Javaに似てるので、javaから移植しやすい。javaプログラマーは入りやすい。
  • javaと比べると、文句なしでDartがいい。全然違う。Objective-Cはクソ
  • 新しめの言語なので、変な制約が少ない。C++とかクソ
  • IDEはVSCode使ったが、軽くて良い。最近のXCodeはクソ。androidStudioでも開発可能。
  • 慣れるとサクサク開発できて、超気持ちいい。
  • Dart関係ないけどVSCodeでのソース自動整形はいい。生産性が上がる。(今まで興味なかった)
  • webGLも使える。StageXLはでふぉでON。まあ古めのデバイスのためにOFFったけど。新しめのデバイスならONの方が性能上がりそう。
  • JSは結構デカくなるけど、gzip圧縮できるので、問題無し。このアプリは748KB->212KBだった。
  • ChromeのみでDartソースレベルDEBUGができる!さすが純正。

悪かった点

  • VSCodeだと日本語コメントが文字化けしやがる。(最新版では修正済みらしい)
  • 日本語情報が少ない。特にStageXL情報がない。全くない。
  • 画面をコードで構築するので、めんどい。まあこれはこれでいい。変なツールを覚える必要がない。
  • たまにソース辿れない例外が出て、修正に苦労する。
  • double->intの暗黙の変換で整数じゃないと例外が出てしまう。
  • VSCodeからDEBUG起動とかもできるが、そのうち劇重になるので、使わなかった。Chromeのみでデバッグ可能なので、そっちを使う。現在は改善されてるかも。
  • 言語別リソースの機能とかは無い。仕方ないので、自分で構築した。まあ言語ごとにjsonに追い出したりするだけ。
  • ビルド待ち時間が少ないので、疲れる:sob:超疲れる。news読む暇なくなる。

最後に

概ねサクサク作れました。:v:
生産性、いいわあ。:relaxed:ビルド速いのは正義だねえ。

StageXL使ってる日本人、私だけかもー。貴重な老人プログラマーになれたかも。

そのうちFlutterもやろう。
AngularDartは現在使用中。

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

PageSpeedスコアを左右する「ロングタスク」を体感する

PageSpeedスコアの「インタラクティブになるまでの時間」

PageSpeedスコアが計算される上で、最も重視される指標が「インタラクティブになるまでの時間」(TTI)です。

PageSpeed Insightsの点数はどのように計算されているか。100点をとるための条件

この「インタラクティブになるまでの時間(操作可能になるタイミング)」について、公式では以下のように説明されていますが、

「操作可能になるタイミング」とは、レイアウトが安定して、主要なウェブフォントが表示され、メインスレッドでユーザー入力を処理できる状態になるタイミングとして定義されます。
https://developers.google.com/web/tools/lighthouse/audits/time-to-interactive?hl=ja

もう少し詳しく調べてわかったことを以前、インタラクティブになるまでの時間(TTI: Time To Interactive)とは何かにまとめました。

50ms以上の「ロングタスク」

Lighthouseのソースコードとしては以下のファイルにロジックが記述されていることがわかり、上記の考察はおおよそ合っていたと理解しました。
/lighthouse-core/computed/metrics/interactive.js

実際ほとんどのページでは、初期段階に起こりがちな重たい処理(ロングタスク:50ms以上メインスレッドを専有する処理)が、5秒以上収束するタイミングをインタラクティブになるまでの時間と呼ぶこととなります。

ロングタスクは、JavaScriptが引き起こす以外にも、レンダリングやペイントによって発生することがあります。

ついでに言うと、公式の説明にある「レイアウトが安定して」「主要なウェブフォントが表示され」という部分は実際、見てなさそうです。

ロングタスクを駆逐せよ

TTIについてのスコア改善の理想は(実際は難しいのですが)、50ms以上メインスレッドを専有する「ロングタスク」をJavaScriptから無くすことです。

ロングタスクが一切発生しなければ、早いタイミングでインタラクティブになるまでの時間を迎えます。その場合は、DocumentContentLoadedイベントの終わりか、重たいレンダリング処理の終わりでTTIが評価され、PageSpeedスコアは高得点が期待できます。

しかし、たかだか50msの処理がどれだけ応答性を阻害するのでしょうか?

それをとりあえず体験してみる環境を作ってみました。

ロングタスクを体験するサンプルページ

ロングタスクとして、メインスレッドで空のforループを回すダミータスクを定期的に発生させてみます。

実際のWebページに近いサンプルとして、Colorlibで無償公開されているテンプレートをいくつか拝借しました。

スマホでの閲覧をお勧めします。
https://www.ideamans.com/_samples/long-tasks/

いずれかのサンプルを開くと、こんな感じで右下に簡単なコントロールパネルが表示されます。

Neuron_HTML_CSS_Template.png

  • LONG TASK ≧ 50ms 50ms以上のロングタスク実行中に点灯
  • Dummy Tasks ダミータスクを発生させるか(ON/OFF)
  • Duration Mean ダミータスクの平均所要時間(ms)
  • Task Interval ダミータスク間の間隔(ms)

Applyで設定を変更します。Indexでサンプルテンプレートの一覧に戻ります。

ダミータスクの所要時間は、Duration Meanを期待値(母集団の平均値)とする正規分布に基づき、乱数で決めています。パラメータをいろいろ変えて試してみてください。

ロングタスクなし ロングタスクによる引っかかり

素のスクロールにはあまり影響しない

上記のサンプルページではスムーススクロール系ライブラリが利用されているため、スクロール操作で引っかかりまくりですが、そうでないページではスクロールに大きな違和感はありません。

例えば、次のサンプルページは、JavaScriptによるカルーセルスライダーのひっかかりはあるものの、ページ自体のスクロールはなめらかです。

スクロール系ライブラリなし

まとめ

閲覧中心のページではロングタスクを感じにくい

静的なコンテンツをスクロールで閲覧するだけの読み物系のページでは、ロングタスクによるストレスは感じにくいと言えるでしょう。

これが確認したかったことのひとつでした。多くのWebページは上記のような目的なので、トラッキングタグなどによるロングタスクによってPageSpeedスコアが低迷しても、「遅いとは感じない」という認識の不一致を招きがちです。

PageSpeedスコアの「インタラクティブ」の条件は厳しい

ロングタスクは引っかかりを起こすものの、まったく操作できなくなることはありません。言葉の印象としても、インタラクティブとは性能はさておきユーザーの操作への応答性自体の有無を示すような気がします。

ところがPageSpeedスコアにおいては、50msという短い引っかかりも許さない非常に高い応答性能を示す状態インタラクティブと評価します。

かなり厳しい基準です。Google AnalyticsやAdSenceなど一般的なサードパーティスクリプトを導入するだけで、通常はスコアが下がります。

このサンプルでは引っかかりがわかっているから許容できるものの、急に起きたらストレスだろうなぁ…とも思います。

なくせないロングタスクは遅延させる

サードパーティーのロングタスクは手を出せないので、遅延させるしかありません。

例えば広告表示のスクリプトであれば、少しスクロールされたタイミングで起動すると、PageSpeedスコアの採点からは逃れることができます。

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

AngularでCloud Storage for Firebaseにファイルアップロード(キャッシュとか)

記事の目的

Firebase便利ですよね。
なかでも簡単にファイルアップロードは便利です。

こちらに手順を記載されている記事があります
https://qiita.com/daikiojm/items/1c5940d618b3b644439a

  • downloadUrlが非推奨になっていること
  • キャッシュの設定とか

の追記だけしておきます。

早速コードの箇所だけ

Material Designを利用していますが、使わなくても大丈夫です。
(ボタンをおしゃれにするために、inputフィールドを隠しています。)

<!-- テキストファイルを選択するinput -->
<div id="input-area">
  <input #fileUpload hidden type="file" value=""
         (change)="selectFile($event)"/>
  <button
      (click)="fileUpload.click()"
      type="button"
      mat-button color="primary">ファイル選択
  </button>
  <p *ngIf="selectedFile">アップロードファイル: {{selectedFile.name}}</p>
  <button [disabled]="!selectedFile"
          mat-stroked-button color="primary"
          (click)="upload();">Upload
  </button>
</div>

<!-- アップロードしたファイルを表示するエリア -->
<a *ngIf="uploadFileUrl" [href]="uploadFileUrl">{{ uploadFileUrl }}</a>
  // selected file
  public selectedFile;

  // ファイルのURL
  public uploadFileUrl;

  constructor(
    private storage: AngularFireStorage,
    private store: AngularFirestore,
  ) {
  }

  selectFile(evt: any) {
    // ファイルを未選択で閉じた場合など。
    if (!evt.target || !evt.target.files || evt.target.files.length !== 1) {
      this.selectedFile = null;
      return;
    }
    this.selectedFile = evt.target.files[0];
  }

  async upload() {
    if (!this.selectedFile) {
      return;
    }

    // ファイルをUIDごとに分けるとか
    const filePath = 'uploads/' + 'uid等でフォルダを分ける' + '/' + this.selectedFile.name;

    // ストレージの参照をもつ
    const storageRef = this.storage.ref(filePath);

    // putまたはputStringであげる
    const result = await storageRef.put(this.selectedFile, {'cacheControl': 'public, max-age=86400'});
    console.log(result);

    // storageの参照からダウンロードURLを取得(先の例にあったようにobservableのまま受け取ってもOK)
    storageRef.getDownloadURL().subscribe((result) => {
      this.uploadFileUrl = result;
      // ここでStoreにユーザがアップロードしたファイル一覧に保持とか
    });
  }

ほぼ同じ方法で
blobやbase64もできます。
https://firebase.google.com/docs/storage/web/upload-files?hl=ja

    const result = await storageRef.put(this.selectedFile, {'cacheControl': 'public, max-age=86400'});

ここで例えばキャッシュをもたせる場合は、
上記のように、putのオプションとして設定可能です。

雑ですいません。

Angular最高!!

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

A-FRAME: 物理演算でボーリングっぽい動きを実現してみる5(ボールの回転への抵抗)

A-Frameをつかって物理演算ができるようにしてみます。
回転に対する抵抗をみていきます。角減衰と言うようです。

回転に対する抵抗が増えたらどうなるのか、ちょっとイメージが湧かないです。
ボールは転がっているので転がりにくくなるのか、、、それだとこのボーリングの例では線形減衰との区別がつかないです。
とりあえずやってみます。

例1)angularDamping=0.01
デフォルト値です。
ang1.gifdemo

例2)angularDamping=1
抵抗を最大にしてみます。
ang2.gifdemo
ピンと衝突した時に、ブレーキがかかったように止まりました。

例3)angularDamping=0.0001
抵抗を小さくしてみましょう。
ang3.gifdemo
0.01の時と比べて、衝突時に受ける影響が少なくなっているように見えます。

例4)angularDamping=0
抵抗を無くしてみましょう。
ang4.gifdemo
0.0001との差は感じません。

まとめ

このボーリングの例だと、角減衰はボールとピンの衝突時に影響する値のようでした。
ピンからの反作用が減衰するという事でしょうか、ちょっとイメージがついていません。
とりあえず、デフォルトの0.01よりは0.0001の方がボーリングらしい動きに見えるので、0.0001を採用します。

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

映画館の近くに住みたくなった時に便利なサイトを作る

映画館の近くに住みたい。

東京ならとりあえず渋谷か新宿あたりに住めば良さそうだけど、大阪や京都だったらどこだろうかと気になったので、映画館がたくさんある駅がわかるサイトを作りました。映画館の近くに住みたくなった時の参考にしていただけたら幸いです。

Google Mapsのプレイスを初めて使ったので、そこら辺もまとめています。Maps関連のAPIは、気前のいい無料枠が設定されていますが、調子に乗って使いすぎると危ないので注意が必要です。

not-far-from-home.gif
映画館の近くに住みたい

「映画館が近くにたくさん」の定義

駅から半径2kmに映画館が何個あるか。
同数の場合は、距離の総和が小さい方を上位とします。
2kmについては、キリがいい以上の理由はないです。

アンケートによると、徒歩圏は10分以下と考えている人が多いらしいのですが、さすがに800mじゃ映画館が集まらなそうだったので・・・歩こう。

2012年4月20日「不動産広告に関するアンケート」調査結果

情報の取得

「映画館」と「駅」の名前と緯度・経度が必要。

Google Mapsで映画館のデータ

映画館のデータを提供してくれるような、ニッチなAPIはなかったので、Google Mapsのプレイスで、映画館の名称と緯度・経度を取得しました。使いたいNearby Search requestsの検索範囲が、最大で半径50kmという制約があったので、そこを気合で解決しつつ。

映画.comなどのサイトをスクレイピングできれば一発ですが、シンプルに転載になりそうなので・・・最後の答え合わせに、いくつかの映画情報サイトを見比べましたが、それは問題ないでしょう。

下準備 - 緯度・経度を用意

半径50kmが最大なので適当なポイントを決めよう

とりあえず県庁所在地で => 足りない

やり過ぎ感はあるが全国の市役所で => データどこにある

住所といえば郵便局 => 事業所の個別郵便番号データ

スプレッドシートで市役所に絞る

Geocoding APIで住所から緯度・経度へ

使用・再配布・移植・改良について
大口事業所個別番号データに限っては日本郵便株式会社は著作権を主張しません。自由に配布していただいて結構です。日本郵便株式会社への許諾も必要ありません。

大口事業所個別番号データの説明

住所といえば郵便局という安直な発想で調べてみると、事業所の個別郵便番号データなるものに、配達物数の多い大口事業所の一覧があり、だいたいの市役所がありそうだった。厳密に全市役所を網羅したいわけではないので、これで事足りそう。

const googleMapsClient = require('@google/maps').createClient({
  key: 'API_KEY',
  Promise
})

async function main() {
  const data = await googleMapsClient.geocode({
    address: '北海道札幌市中央区北一条西2丁目',
  }).asPromise()

  const { lat, lng } = data.json.results[0].geometry.location

  console.log(lat, lng) // 43.0634672 141.3544036
}

main()

Geocoding APIを使うと、こんな感じで住所から緯度・経度を引けます。ライブラリは @google/maps を使いました。

hokkaido_tarinai.png

全国の市役所で大体カバーできましたが、北海道などで微妙に隙間ができてしまったので、手動で緯度・経度を引っ張って来て埋めました。

映画館検索

const googleMapsClient = require('@google/maps').createClient({
  key: 'API_KEY',
  Promise: Promise
})

async function main() {
  // 札幌市役所
  const options = {
    radius: 50000,
    language: 'ja',
    location: '43.0634672,141.3544036',
    type: 'movie_theater',
  }

  const data = await googleMapsClient.placesNearby(options).asPromise()

  console.log(data.json.results)
}

main()

[
  {
    geometry: { location: [Object], viewport: [Object] },
    icon: 'https://maps.gstatic.com/mapfiles/place_api/icons/movies-71.png',
    id: '1661e774928b2c7d5c02c6d9a0ed944371cfd809',
    name: '札幌シネマフロンティア',
    photos: [ [Object] ],
    place_id: 'ChIJJ0N9wXQpC18Rt_ADN1jHKak',
    plus_code: { compound_code: '3992+59 日本、北海道 札幌市', global_code: '8RM33992+59' },
    rating: 4,
    reference: 'ChIJJ0N9wXQpC18Rt_ADN1jHKak',
    scope: 'GOOGLE',
    types: [ 'movie_theater', 'point_of_interest', 'establishment' ],
    user_ratings_total: 793,
    vicinity: '札幌市中央区北5条西2丁目5−番地 JRタワーステラプレイス7F'
  },
  ...
]

あとは、集めた緯度・経度をもとに、Nearby Search requestsのパラメータで、typemovie_theaterを指定して、ひたすら取得しました。

表記修正・目視チェック

MOVIX Amagasaki => MOVIX あまがさき

パラメータで日本語を設定したはずなのに、英語やローマ字になっているところが結構あったので手動で修正しました。映画の興行会社や成人向けの映画館、そもそも映画館じゃない施設も含まれていたので、こちらも地道に手動で修正。最終的に、全国約550箇所の映画館を確認したので、丸一日かかってしまった。

駅データ.jp

名前がそのまんまの、ありがたいサービス。
駅データの利用活性化のために、データを無料で公開しているとのこと。
フリガナやローマ字を含む完全なデータが欲しい場合には、有料データもあります。

APIでも提供されていますが、一括で欲しかったのでCSVで。

駅データ.jp

station_cd,station_g_cd,station_name,station_name_k,station_name_r,line_cd,pref_cd,post,add,lon,lat,open_ymd,close_ymd,e_status,e_sort
1110101,1110101,函館,,,11101,1,040-0063,北海道函館市若松町12-13,140.726413,41.773709,1902-12-10,,0,1110101
1110102,1110102,五稜郭,,,11101,1,041-0813,函館市亀田本町,140.733539,41.803557,,,0,1110102
1110103,1110103,桔梗,,,11101,1,041-0801,北海道函館市桔梗3丁目41-36,140.722952,41.846457,1902-12-10,,0,1110103

ランキングの作成

データは揃ったので、駅と映画館の距離からランキングを作成していきます。
緯度・経度から出す値なので、駅から映画館までの道のりではなく、直線距離です。

緯度・経度の差から距離を計算

最初は、計算方法を調べてライブラリを使わずにやっていたんですが、だんだんと計算式が複雑になっていき、緯度・経度からの距離計算とか需要が多そうだし、良さげなライブラリあるでしょ、と思って探したらあった。ダウンロード数(80k/週)、スター(2.8k)、ライセンス(MIT)、更新日(1時間前)あたりを参考に決めました。
manuelbieh/geolib

getDistance と getPreciseDistance

このライブラリでは地理の計算全般に使える便利な関数が提供されていて、緯度・経度から距離を算出できる関数は getDistancegetPreciseDistance の2つがありました。名前のとおり正確さの違いなんですが、前者は地球を球体として考えた場合の簡易的な計算になっており、後者はVincenty法の逆解法を使っているため精度が高いぶん時間がかかるとのこと。公式と説明を見てもピンとこなかったので、詳しく聞かれたら困る。

フロントエンドで使うわけではなく、バッチでまとめて計算するので、精度が高い方で。

getDistance.ts
// 簡易的な計算
// https://github.com/manuelbieh/geolib/blob/master/src/getDistance.ts
const distance =
  Math.acos(
    normalizeACosArg(
      Math.sin(toRad(toLat)) * Math.sin(toRad(fromLat)) +
        Math.cos(toRad(toLat)) *
          Math.cos(toRad(fromLat)) *
          Math.cos(toRad(fromLon) - toRad(toLon))
      )
  ) * earthRadius;
getPreciseDistance.ts
// Vincenty法の逆解法
// https://github.com/manuelbieh/geolib/blob/master/src/getPreciseDistance.ts

// Calculates geodetic distance between two points specified by latitude/longitude using
// Vincenty inverse formula for ellipsoids. Taken from:
// https://www.movable-type.co.uk/scripts/latlong-vincenty.html

計算してみる

const { getPreciseDistance } = require('geolib')

const shinsaibashi = { latitude: 34.675012, longitude: 135.500330 }
const burg7 = { latitude: 34.7005079, longitude: 135.4984645 }
const distance = getPreciseDistance(shinsaibashi, burg7)

// 心斎橋駅 → 梅田ブルク7 = 2,834m
console.log(distance)

Google Map上の直線距離と比べてみる

Google Map上で 直線距離を図る機能 があったので、計算結果と比べてみました。
2地点をクリックしないといけないので厳密ではないですが、大きな差はなかったのでこれで良しとします。

名前 計算結果 Google Map
シネマート心斎橋 334m 334m
TOHOシネマズ なんば 1,055m 1,060m
なんばパークスシネマ 1,586m 1,590m
シネ・ヌーヴォ 2,283m 2,280m
日劇東映 2,602m 2,610m
新世界国際劇場 2,627m 2,630m
梅田ブルク7 2,834m 2,840m

※ いずれも心斎橋駅からの距離

全駅 × 全映画館 で直線距離を計算

全ての駅と、全ての映画館から、近い組み合わせのみ絞ります。
都道府県などで事前に組み合わせを絞ることも考えましたが、隣県だけど近い、とかもありそうなので全部で。
約1万駅 × 約550館 なので結構な量ですが15秒ぐらいで終わりました。マシンに感謝。

ランキングのJSONを作成

あとは都道府県ごとに、映画館が多い順にソートしてランキングを作成。
まるっとJSONで吐き出してS3に置きました。

[
  {
    "prefId": 1,
    "stations": [
      {
        "id": 9910109,
        "lineIds": [
          99101,
          ...
        ],
        "name": "大通",
        "point": 45.3,
        "cinemas": [
          {
            "id": 1,
            "distance": 0.7
          },
          ...
        ]
      },
      ...
    ]
  },
  ...
]

フロントエンドの構築

フロントエンドは、降ってくるJSONを表示しただけなので割愛します。TypeScriptじゃないNuxt.jsです。普通に作ってもつまらないと思い、 text-align: center 縛りという不可解な制約を設けたので、中央に寄ってます。

not-far-from-home.gif
映画館の近くに住みたい

いろいろな問題と解決策

駅とランキングにまつわる問題がいろいろ。

同名の駅が多すぎる問題

東京駅や渋谷駅などのターミナル駅は、複数の路線が通っているため、ランキングが同名の駅で埋め尽くされた。だいぶ雑な解決方法ですが、1km以内で同名の駅は大差ないという事にしてしまって、路線の情報だけマージすることに。

同名なのに遠くにある駅が存在する問題

だいぶ大雑把に解決したつもりが、それでもダメだったのが石川県の「野々市駅」。
JR北陸本線と北陸鉄道石川線の2つの駅があり、約2kmも離れていたので、別の駅として扱いました。

同名で遠くの駅を調べている過程で知ったんですが、京都アニメーションの最寄り駅の木幡駅は、京阪が「こわた」で、JRは「こはた」と読むらしい。

大阪・梅田・大阪梅田問題

もう面倒になってしまったので、別の駅としました。

渋谷駅が弱すぎる問題

感覚的には、渋谷駅が圧倒的な1位だと思っていたんですが、トップ5にも入らないという問題が。
代わりに1位になっていたのが代官山。代官山が映画館に行きやすい・・・?

ランキングの算出が、単純に半径2kmにある映画館の個数だったため、そこそこの距離にたくさん映画館がある絶妙な駅として、代官山がトップになっていました。渋谷にも行けるし、目黒にも恵比寿にも行ける。しかし、どれも遠い。これを1位にしてしまうと実感から離れてしまうので、距離で重みを付けよう。

映画館まで徒歩で行くとすると、20分以上は辛いので、80m/分で歩くとして1.6km以上をベースの10点に設定。それと比較して徒歩1分(以下)がどれぐらい嬉しいかと考えた時に、せいぜい倍ぐらいじゃないかという事にして、その間は点数が線形で変化するように。とても適当。

結果、晴れて渋谷駅が1位に。これでいいのか。(作為が過ぎる)

const BASE_POINT = 10
const ACCEPTABLE_DISTANCE = 1.6

function distanceToPoint(distance) {
  if (distance > ACCEPTABLE_DISTANCE) {
    return BASE_POINT
  }

  return BASE_POINT * (1 + (ACCEPTABLE_DISTANCE - distance) / ACCEPTABLE_DISTANCE)
}

埼玉の1位は熊谷で良いのか

熊谷が嫌いなわけではなく。

小さい頃からJR高崎線に育てられたので、熊谷にはむしろ親近感があるんですが、1位って。駅から行ける距離に映画館が2つあるから1位になっているだけなんですが。埼玉で映画の見やすさを考えた場合、東京への出やすさも重要なパラメータになりそう。そこまでやる根性はないので、とりあず熊谷おめでとう。あついぞ。

気になった映画館

全国の映画館を調べている中で気になったところを。

空港に映画館

アニメの制作会社が運営

サイトがかわいい

規模の小さい映画館は、Wixで作ったみたいなサイトが多い中で、デザインが可愛かった。尾道、行きたいけど少し遠い。

和歌山はジストシネマで持っている

県内の5つの映画館のうち4つがジストシネマの系列。

盛岡の映画館通り

北海道・東北エリアで見比べた時に、盛岡の映画館が多かったので調べたところ、「映画館通り」と呼ばれる素敵な通りがあるらしい。最盛期の昭和30年代後半には、映画館通りを中心に15館もの映画館があったとのこと。

北海道のポツンと離れた映画館

北海道の市街地から離れた浦河町というところにポツンとあった大黒座が気になった。行ってみたい。

月に1本を選ぶ映画館

静岡の清水駅前にある夢町座という映画館。
毎月8日間限定で、こだわりの1本を上映するらしい。

マージャンができる映画館

名前だけ見ると中華料理屋さんのような玉津東天紅とう大分の映画館。
施設内で卓球や健康マージャンを楽しめるらしい。そこまで広くなさそうな施設なので、どうやって麻雀と映画が共存してるか気になる。

この映画館とは関係ないですが、麻雀放浪記2020という映画がビックリするほどつまらなかったので、時間がある方は是非。自分が合わなかっただけで、刺さる人がいるかもしれない。

電気館

歴史ある電気館で、現在も運営されているのは調べたところ熊本と高崎のふたつ。

108年の歴史

明治44年に開館した、新潟にある映画館。

〇〇座

大黒座,御成座,川越スカラ座,シアター駒鳥座,あまや座,宇都宮ヒカリ座,伏見ミリオン座,夢町座,進富座,岡谷スカラ座,伊那旭座,東座,ほとり座,出町座,八丁座

〇〇座がたくさん。札幌にあった蠍座がきっかけで映画館を好きになったので、閉館してしまって寂しい。

T・ジョイが運営する映画館の命名規則

${地名}${ドイツ語}${スクリーン数}

梅田ブルク7
新宿バルト9
鹿児島ミッテ10
広島バルト11
横浜ブルク13

T・ジョイ〇〇というシンプルなもの以外に、ドイツ語を挟んだシリーズがある。個人的には梅田ブルク7の響きが一番好き。

これらの命名と異なる「こうのすシネマ」も、市民ホール融合型という珍しい映画館。鴻巣市がT・ジョイに運営を委託してるらしい。

後半がほとんど映画館の話になっちゃいましたが、以上です。

Webサイト
https://not-far-from-home.cc

GitHub
https://github.com/nishinoshake/not-far-from-home

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

google-home-notifierを使ってGoogleHomeに喋らせる

※こちらの記事は株式会社ギフトパッド「システム開発部技術委員会」の11月発表内容です

GoogleHomeは基本的にこちらから「OK!Google」と話しかけることをトリガーに何かしらの処理を行ってくれますが、こちらからの指示がなければただの置物です。
今回は「google-home-notifier」というnpmパッケージを使って、Slackで投稿した内容をGoogleHomeが発言してくれるようにします。

google-home-notifier

https://github.com/noelportugal/google-home-notifier

インストール

npm install google-home-notifier

実装

「google-home-notifier」READMEのサンプルを参考に、通知用のjsを作成
とりあえず喋る言葉を固定にする

const googlehome = require('google-home-notifier');
const language = 'ja';

googlehome.device('Google Homeの名前', language);

googlehome.notify('しゃべったあああああああ', function(res) {
  console.log(res);
});
node notice.js

GoogleHomeと実行環境が同じネットワークに繋がっていれば、GoogleHomeから喋るようになります。

※現在node_modules/google-home-notifier/package.jsonの「google-tts-api」を「0.0.4」に指定して、npm updateかけないとエラーで止まります。

nodeでwebサーバーを作成する

http://localhost:3000/ にリクエストするとGoogleHomeが喋るようにします。

const http = require('http');

const googlehome = require('google-home-notifier');
googlehome.device('Google Homeの名前', 'ja');
googlehome.ip('Google HomeのIP');

http.createServer(function (req, res) {
    req.on("end", function() {
        googlehome.notify('しゃべったあああああああ', function(res) {
            console.log(res);
        });
    });

    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end();

}).listen(3000, '127.0.0.1');

localhostを外部公開

ngrokを使って外部公開してSlackから叩けるようにします

$ngrok http 3000

Session Status                online
Session Expires               7 hours, 58 minutes 
Version                       2.3.35
Region                        United States (us) 
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://c4c72050.ngrok.io -> http://localhost:3000
Forwarding                    https://c4c72050.ngrok.io -> http://localhost:3000  

Slackとの連携

SlackAPIのOutgoing Webhookを使って、「https://c4c72050.ngrok.io」 をエンドポイントに指定

SlackからPOSTされてきたテキストを喋らせるように変更

const http = require('http');

const googlehome = require('google-home-notifier');
googlehome.device('Google Homeの名前', 'ja');
googlehome.ip('Google HomeのIP');

http.createServer(function (req, res) {
    let data = '';
    req.on('data', function(chunk){
        data += chunk;
    });

    req.on("end", function() {
        const qs = require('querystring');
        const post = qs.parse(data);

        const text = post['user_name'] + post['text'];
        googlehome.notify(text, function(res) {
            console.log(res);
        });
    });

    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end();

}).listen(3000, '127.0.0.1');

今回は動くところまでを目標にしているのでやってませんが、tokenも送られてくるのでチェックすることもできます

これでSlackの投稿をトリガーに、GoogleHomeに喋らせることができます。

参考文献

https://qiita.com/ktetsuo/items/21f4f85a98145772be3d

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

【初心者向け】 プログラミング「独学1ヶ月」と「スクール入学3日」との比較

初投稿なので不備あれば気軽にご連絡いただければ幸いです。
この記事はプログラミング初心者で、独学中の人、プログラミングスクールに通うか迷っている人の参考になるかと思って書いてみました

自己紹介

プロフにもありますが、今まで保険代理店で成果報酬型の営業職をしておりました。
手取も多く、残業も月5時間程度で人間関係も良好な職場だったのですが、

「興味のある分野で、プロダクトを売り込むんじゃなくて創ってみたい!」

という気持ちの変化で現在はとあるプログラミングスクールで勉強しています。
(宣伝目的ではないので名前は出していません)

独学1ヶ月での成果

(独学は個人差が大きいので1つのサンプルとしてご覧ください)
8月に独学を開始して1週間かけて作ったものが ↓ になります

やったことは初心者御用達のProgateのHTML.CSSだけです。

このLTページ作成後、JavaScriptとjQueryを一通りやって理解力が追いつかず、学習進度も低下

残り3週間については、
レスポンシブ、簡単なアニメーション動作、口コミ機能を追加させただけで1ヶ月が終了です。
(9月は書籍購入や現実逃避して進捗はありません)

スクール入学

作ったものは ↓ です (※提出日に投稿したので5日目のツイートになっています)

 
 
で、提出日の夜にバグを取り払ってJavaScriptで作ったゲームが ↓ です
先ほどの投稿の最後でエラーだったページの内容です

テトリミノごとに違う画像を使用して、揃ったときにチーズの焼ける音を挿入しています。

テトリス部分は3日目時点では完成せず、6日目で完成しました。
(4日目に参考書を1冊読み切ってから制作に取り掛かったため)
 
 

独学していた頃との比較

全くの初心者とのころとは完全な比較と言うのは難しいかもしれませんが、

  1. 3日目の時点で1ヶ月の成果物同等程度、
  2. 6日目で苦手分野へ挑戦して制作できた

これには独学当時と大きなの差があるように実感しました!

「スクールで学んでできた!」というモノではなく
お題に沿った 面白そうなモノをとりあえず作ってみよう という
好奇心から意外と学習が捗った… というのが学習進捗の差かと思います。
(Progate等も面白いのですが、コレが作りたい!とはちょっと違うので)
 
講義というよりは 「学習環境」 が自分を後押ししてくれたと思っています(後術します。)
 
 

スクールで3日間学んでいたこと

入学して3日間の内容は
1日目:HTMLの基本(h1とかpとか初歩的なこと)
2日目:CSSのレイアウトについて(flexboxの扱いとか)、レンタルサーバーの説明とか
3日目:UI/UXの講義
 

…内容としては深いことは何もしていません。
Progateでもやっていそうな内容ですし

授業で勉強したのも ↓ みたいな感じです。(一部ですが)
スクリーンショット 2019-11-24 19.48.31.png

 
で、週末の課題として配布されたPDF通りのLPを作ってサーバーにアップすること(猶予3日間)
必須事項はこれだけです。

 
※ただ、これだけで終わるような課題ではありません

[プラスで余裕があれば機能を追加してきていいですよ?]

必須課題だけで終わらないのが今回入学したスクールの特徴でして
 
私もテトリスの実装(1日間に合わずですが)したのですが、
同期の中には以下のような機能を実装していました
 
 
・音楽に合わせてひょっこりはんが出てくる仕様
・ある場所をクリックするとページ全体でモグラ叩きゲームが始まる
・課題PDFを全て自分好みなデザインに変更する
 etc...
 
 
このスクールの特徴なのですが、
「課題終わったからここまででいいや」 がなくて
「ここまでできたから次はこれを付けてみよう」 といろんな技術を各々で見つけて実装してきます

この投稿書いているのが通学して1ヶ月半経った今なのですが、この風習は変わらずです?
 
授業で教わるというよりは「この機能つけるには何を勉強するべきか」がこの場では一般化していて
勝手に自走力が身についている といった環境なので、毎週の課題発表が楽しみです!?
自分が作りたいものを毎回作っているので夜までコードを書くのがそこまで苦ではない…と思う(笑
(本音は結構キツイので好きなモノの制作でないと続いてないと思う)

なぜ今この記事を書いたのか

2点あって
①フォロー中の人が2ヶ月経過してもHTML.CSS道場ばかりやっている人が意外といて、
 簡単なLP作成の記事を有料で購入していたり、次に進んでない人が意外と多かった

②先日Twitterのタイムラインに
 「独学とスクールどっちがいいの?」 議論みたいなのがあったので

「確かにスクールいくか悩んでるときに参考になるものってなかったよなあ」(アフィカスばかりで…)

と思って案外需要あるかもと思い、振り返りの意味も込めて書いてみました!笑


 
 
私の意見としては 「結局はプログラミングは独学です」 ってのが1つ
色々意見はありますが、

 
未経験の繋がりが欲しい
 →目的意識違う人たちだったらその繋がり1年も持たないよ(キツイとか、ノマド飽きたとか)
  ↑なら経験者の繋がり求めに行ったり、勉強会の方が70万よりコスパはいいよ

 
一人じゃ続かない
 →メンターでは多分続かないので通学型のスクールか家庭教師つけるとかした方がいいかと

 
同時期に始めた人たちの進捗が気になる
 →Twitterと勉強会、サロンを駆使してください

 
行きたいけどお金がない…
 →(お金自体はあるけど、70万も払う価値あるの?っていうのがここに当てはまるのかと)
  社会人は最近「第4次産業スキル習得講座」っていうものがありますので、検索してください。
  条件が揃えば入学費+日々の生活費の手当が発生するし、国民年金も免除できる可能性があります。
  https://career-theory.net/resign-pension-3477
  学生は親にお願いしましょう。
 

1000万稼ぐフリーランスに…
 →営業と副業やった方が早いし、お金だけ目標にしたところでマジで続かんよ(経験より)
 
 
 
 
 
 
そう思っているのになんで今も通ってるの?? という疑問を持たれるかと思いますが、

学習期間と入学金を投資するだけの価値があると感じたので今も通っています。
一応、動画教材に不備があったり不満はありますが学習環境が気に入っているのが大きいです。

話が長くなりすぎるのでその詳細は後日noteかQiitaに投稿します

最後に

独学か悩んでいる方は
 まずは1ヶ月くらい独学で周りを見ないで突き進んでみてはいかがでしょうか?
 やってみて周囲と遅れていたり挫折したら何かのサービスを使ってみると良いかと思います

スクールにこれから行く方は
 入学前に「ここに自分が投資して納得できるのか」しっかり考えましょう
 くれぐれも考えすぎはダメ(私はここに時間かけ過ぎて後悔してます…)
 
 
 
 
偉そうなこと書いてますが、まだ勉強初めて1ヶ月半のペーペーです←
phpやJS触ってますが、今でもtableタグとか理解してなくてぶつかることもあります笑

ただ、全部理解して進むのではなく、ある程度型が作れるようになったら
興味の向くままに勉強する方が進捗ありますし、何より楽しいです!!


 
 
今は苦手のJS克服しつつphpに取り組んでいますので、よかったらTwitterフォローなりメッセ下さい。
(とても喜びます←)
 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

1コマアニメをJavaScriptで実装

1コマアニメとは

  • 世の中には「1コマアニメーション」(ひとこまあにめーしょん。以下、1コマアニメ)という表現メディア?のジャンルが存在するらしい。

(1コマアニメを アニメGIFに変換した例 ↓ )
色即是空.gif

  • 要は、アプリ(ソフト)上で1枚の絵を描いた過程が記録され、再生できるというもの。
  • そのメリットや可能性について、参考記事(注1)で詳しく述べられている。
  • また、その記事によると、1コマアニメを実現するための無料ソフト(PEAS motch! / 9VAe)も配布されている。

ソフト(アプリ)方式の問題点

  • 無料ソフトとはいえ、当然ながらインストールが必要で、これがかなりのハードルとなる。
  • インストールは手間でもあるし、スマホ等では容量の余裕も少ないことがある。
  • 怪しいソフトはセキュリティの心配もある。 (最近のOSでは、怪しいソフトを入れたり実行したりすると警告が表示され、躊躇される。)

動機・やってみたこと

  • 「1コマアニメ」だけの機能であれば、数十ステップのプログラムで実装できるかも? というわけで、
  • HTML 内の JavaScript で「1コマアニメなんちゃって」を実装してみた。

http://35.237.23.189/onepager/onepager.html

  • JavaScript は、当然ながらサーバから端末側のブラウザに送信し、処理をおこなう。
  • 参考ソース 注2 を絶大に参考にさせていただきました! ほぼ fork ?)
  • Android 9 で動作確認。iOS でも動くかな?

JavaScript による実装のメリット

  • 対応ブラウザがあればインストール不要。
  • 機器や OS の違いを吸収できる。
  • セキュリティも、アプリよりは、まぁ安心。

苦労したこと

  • スマホのタッチスクリーンに対応したこと。
     (マウスとはイベントが異なるので、専用の実装が必要。PCだけではデバッグしにくい。)
  • JavaScript には、sleep 関数が組み込まれていないため、タイマーで二重ループを遅延させるてゆっくり回すという、ややこしい処理を実装した。

できてないこと(できたらいいこと)

  • 動画データの保存が未実装。(対応するならサーバへの保存、またはサーバ経由でローカルに保存、となるか?)
  • GIF や mpeg などへのフォーマット変換ができていない  (静止画ダウンロードのみ対応。)
  • 線の表現効果は色と太さのみ対応。濃さとかビヨビヨ線とか未対応。
  • 2コマ以上のアニメ制作ができない。(できなくていいけど)
  • [絵をほぞん] 機能にブラウザ依存?のバグあり。

参考資料

注1

参考記事:1コマアニメーション(書き順アニメ)の作り方
https://qiita.com/danjiro/items/8dd0d7c7263f62189437

注2

参考ソース:canvasでのお絵かきソフトの作り方と実例
https://pasonyu.com/how-to-make-drawing-software-with-canvas/

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

【jquery】画面スクロールボタンの実装からイベント発火の基本を復習する

目的

jqueryを用いて画面をスクロールさせるボタンを実装します。
基礎の復習も兼ねてかなり初歩的な部分も調べつつまとめています。

まずは完成形から

HTMLにてボタンを配置

<!--スクロールしたい範囲-->
<div class="contents">
</div>

<!--スクロール機能を持たせる要素-->
<div class="scroll_btn">
</div>

jsファイルに処理を記述

$(function(){
  $(".scroll_btn").on("click", function(){
    $(".contents").animate({scrollTop: $(".contents")[0].scrollHeight}, 500, "swing");
  })
})

jsファイルそれぞれの記述について

$(function(){...})

$(function(){
  //処理したい内容
})

この部分は、
「HTMLの読み込みが全て完了したら、以下の処理を実行しますよ」
という宣言のようなものです。

以下の例のように、javascriptファイルはHTMLのhead要素の部分で読み込みが行われます。

<!DOCTYPE html>
<html>
<head>
  <title>タイトル</title>
  <%= stylesheet_link_tag    'application', media: 'all' %>
  <%= javascript_include_tag 'application' %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

しかし、実際にjavascriptで処理を行いたい部分は
大抵、head要素より下のbody要素の中にあるかと思います。
HTMLは上から順番に読み込むので、順番通りjavascriptファイルが先に読み込まれても、
javascriptファイルにて指定しているHTMLの要素が
まだ認識されていないことになります。

そのため「$(function(){...})」の記述による
「HTMLが全て読み込まれてから」宣言が必要になってくるというわけです。

発火条件の指定(HTML要素を指定し、そこで何が起きるとイベントが発火するか)

//$(function(){
  $(".scroll_btn").on("click", function(){
    //$(".contents").animate({scrollTop: $(".contents")[0].scrollHeight}, 500, "swing");
  })
//})

これは、
「"scroll_btn"というクラスが設定されている要素が"click"されると、
以下function(){}の処理を行います」
という意味になります。

イベントを定義する時の記述として

//$(function(){
  $(".scroll_btn").click(function(){
    //処理
  })
//})

こういう形でも問題なく動きます。
しかし、「.on()」で記述をしておいた方が
複数のイベントタイプを設定できるなどメリットが多いようです。
これについてはまた別の機会にまとめたいと思います。

処理(画面をスムーズにスクロールさせる)

//$(function(){
  //$(".scroll_btn").on("click", function(){
    $(".contents").animate({scrollTop: $(".contents")[0].scrollHeight}, 500, "swing");
  //})
//})
animate()

このメソッドを利用するオブジェクト(今回はcontentsクラスが指定されたdiv要素)が持つ
プロパティなどを徐々に変化させることができます。
今回はscrollTopプロパティを用いて、指定する高さまでスクロールする機能を持たせます。

scrollTop:$(".contents")[0].scrollHeight

contentsが入ったdiv要素のスクロールできる高さ(scrollHeight)を取得(scrollTop)します。

また、animate()の第二、第三引数で、その他スクロールに関するオプションの設定を行っています。

第二引数 500

これは「duration(アニメーションの動作期間)」を設定しています。
初期値は"normal"が設定されており、"fast"、"slow"と指定できます。
また、完了までの時間をミリ秒単位で指定することもでき、
今回の「500」であれば「0.5秒で完了する」ように設定していることになります。

第三引数 "swing"

これは「easing(値の変化の緩急)」を設定しています。
プラグインを入れずに使える値は”linear”と”swing”だけで
"linear"は一定の変化、”swing”は若干の緩急がつきます。
初期値としては”swing”が設定されているようなので、
今回の場合このオプションは必要ないですね。

終わりに

以上、スクロールボタンの実装を軸にした
jquery基本の復習でした。
もし何か誤っていることなどあれば
ご指摘いただけるとありがたいです。

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