Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all 8839 articles
Browse latest View live

Cloud RunでNode.jsのconsole.log, debug, warn, errorでログ出力するとどのように表示されるか

$
0
0

公式に記載の通りCloud RunのログはStackdriver Logging に自動的に送信されます。

下図のデフォルトのログ出力を確認すると500エラーで左のアイコンが !!になっていたり、404エラーで !になっていたりします。

スクリーンショット 2020-01-27 11.20.41.png

Stackdriverのドキュメントを確認すると9つのレベルが用意されており、Node.jsで以下のログ出力を行った場合にどのように表示されるのか確認しました。

console.log('This is log.')console.debug('This is debug.')console.warn('This is warn.')console.error('This is error.')

結果は下図の通りで、全て同じレベルで表示されました。

スクリーンショット 2020-01-27 11.28.32.png

レベルで制御する場合はStackdriver Logging クライアント ライブラリを使用して出力する必要があるかもしれません。


[JavaScript][Node.js]フロントエンドとバックエンドの処理を共通化する

$
0
0

Overview

フロントエンドはReact、バックエンドはNode.jsで作っていると、どちらでも利用したい処理が出てきます。
両方とも同一言語なので、バックエンドをNode.jsにしたメリットを享受できます:relaxed:
…そう思っていたのですが、write at onceかと思いきやモジュールの入出力の違いでそのまま共有することができません。
最終的にはGitHubを介して共有することになりましたが、結果に至るまでの過程を残しておこうと思います。

Target reader

  • JavaScriptとNode.jsを扱っている方。

Prerequisite

  • バックエンドはGoogle Cloud Function(以降、GCF)を利用する、つまりNodeの起動オプションを指定するようなことはできない。
  • Node.jsのバージョンはGoogle Cloud Function(GCF)に依存し、現時点ではV10系とする。
  • フロントエンドはCreate React App(以降、CRA)をビルドに利用し、イジェクトしてWebpackの設定は変更しない。

ここで一貫していることは、自身の管理範囲を最小化すること。

Body

前置き

前提条件に書いていますが、バックエンドはGoogle Cloud Functionを利用します。
GCFは関数を定義するだけなので、Nodeの起動オプションは指定できず、更にバージョンもV10系の縛りがあります。
(Cloud Runを利用すればNode.jsの最新バージョンが利用できそうですが、可能な限りクラウドベンダーに乗っかりたいのでやりません)
フロントエンドについてもCRAを利用するため、BabelやWebPackの設定を変更できない縛りがあります。
自身の環境でこの辺の縛りがなければ、ここでは採用できなかった方法を採用するのもありかもしれません。

問題点

フロントエンドとバックエンドでは以下のように微妙に異なる。

frontend.js
exportconstdisplay=(text)=>console.log("display1:"+text);export{display}// なくてもOK
backend.js
constdisplay=(text)=>console.log("display1:"+text);module.exports={display};

フロントエンドはエクスポートする場合exportキーワードが必須、バックエンドはそれが不要でmodule.exportsに代入する形と少しだけ異なる。
プログラムの条件分岐で何とかできないかと試行錯誤したが、関数の前に入れるexportがどうにもできず断念。

ちなみにmodule.exportsの方式でReactを実行すると実行時エラーになる。

TypeError: Cannot assign to read only property 'exports' of object '#<Object>'

そうなると何とかしてどちらかの記述に寄せる方法を探る必要がある。
それを調べていくことにした。

コピペコース

まずはフロントエンドとバックエンドのソースをコピペによって共有する方法を模索する。
本来コピペは選択するべきではないが、スピード重視であることとどの部分を共有するか明確に決まっていないこともあり一時的な手段として選択する。
チームプレイにおいて属人化は許されないため、原則として後述のパッケージ化を検討するべき。

Node.js側のimport/exportのサポート状況はどうなっている?

現時点のLTSであるV12系では実験的段階(Experimental)のため--experimental-modulesフラグが必要です。
GCFではこのフラグを設定できないため、これは断念しました。(V13系では不要になったようですが、GCFではV14(2年程?)まで待たないといけない)
https://nodejs.org/docs/latest-v12.x/api/esm.html

--experimental-modulesフラグを使った場合については@azawakhさんが書かれています。
https://qiita.com/azawakh/items/8b4a7d3061bddd3b340e

Node.js側でパッケージを利用してimport/exportをサポートできないか?

@issyxissyさんが記事にされていますが、ESMのパッケージを利用するとimport/exportの記述が可能になります。
https://qiita.com/issyxissy/items/f999c06a2b6834c643d3

最初エントリーポイントにrequire('esm')が必須と認識していたため試していませんでした。
しかし、記事を見るとそうでもなさそう。
GCFを使用する場合、エントリーポイントへの介入は厳しいと考えていたため、再度こちらになびくかも?:sweat_smile:

JavaScript側でrequire/exportsのサポート状況はどうなっている?

今回のエラーはWebPackによるものだと判明している。
https://github.com/webpack/webpack/issues/4039

WebPackのTree Shakingにはimport/exportが必須のため、どちらの記述に寄せるかの回答としてはimport/exportが無難の模様。
https://github.com/webpack/webpack/issues/4039#issuecomment-419284940

You've actually using import and module.exports in the same file, which is not allowed by Webpack. You can sidestep this by setting "modules": "commonjs", which will make Babel compile the import to a require. This breaks tree shaking, as mentioned above though, so fixing your code would be a better idea.

Google翻訳先生の解釈

実際には同じファイルでimportとmodule.exportsを使用していますが、これはWebpackでは許可されていません。 これを回避するには、"modules":"commonjs"を設定します。これにより、Babelはimportをrequireにコンパイルします。 ただし、上記のようにTree Shakingを壊すため、コードを修正することをお勧めします。

私の場合、そもそもCRAのイジェクトは実施しない方針のため、Babelの設定自体を公式な方法で変更することができない。

パッケージ化コース

コピペコースを一通り見て、近道しようとしたら遠回りになりそうだったので、パッケージ化に方針を変更する。
いつも気軽に使用しているパッケージだが、自分で作るとなるとどうするの?というのがあったので調べた。

npmは敷居が高そう

npmのドキュメントを見たが、なんか大変そうだなぁという印象:sweat_smile:
何よりプライベートパッケージは有料プランであるのがきつい。
https://docs.npmjs.com/about-private-packages
個人向け、もしくは企業の一人当たりの料金は7ドル/月。
https://www.npmjs.com/products

フロントエンドとバックエンドでソースを共有したいだけなのに7ドルにアカウント管理とか面倒:pensive:
かなりネガティブな印象でドキュメントを漁っていたら…いいのありました!

GitHubを使うという公式裏技?

npmに登録しないとnpmを介したパッケージ共有はできないかと思ったら、GitHubを利用した共有ができる。
https://docs.npmjs.com/configuring-npm/package-json.html#github-urls

npmに登録済みのパッケージとは少し扱いにくい部分もあるが、手軽に共有できるためこれを掘り下げていく。

GitHubを使ってパブリックレポジトリを共有する

GitHubにパッケージを作成

まずはGitHubにレポジトリを作成する。
https://qiita.com/qrusadorz/items/9916644e1af1453fe30b

準備が整ったら早速npm initを実行してpackage.jsonを作成する。

npm init

もしimport/exportの形式を使いたい場合、コピペの時にも出てきたesmを使って以下のコマンドで作成できる。
詳しくは公式ドキュメントを参考にしてほしい。
https://docs.npmjs.com/cli-commands/init.html

npm init esm

基本的にEnterキーで入力なしで進めればいいが、私が入力したのは以下の項目。
変更したいならpackage.jsonを直接修正すればいいので特にここで入力しないといけないというものではない。

  • description
    • パッケージの概要を記入。
  • author
    • 自身のユーザーネームを記入:sweat_smile:
  • license
    • みんな大好きMITで。

これでpackage.jsonが作成される。
npmに公開する意思がないことを明確にしておくため、"private": trueの1行を追加するのをお勧めしておく。
https://docs.npmjs.com/files/package.json#private

esmを使った場合、index.jsとmain.jsが作られるが、esmを使っていない場合は自身でindex.jsを作ってしまおう。
esmを使った場合は、main.jsにexportの形式で記述する。

index.js
constdisplay=(text)=>console.log("display1:"+text);module.exports={display};

これをgit commitしてプッシュすれば、GitHubに反映されるのでパッケージの準備は完了。

プロジェクトにインストールする

プロジェクトにパッケージをインストールする。
例では、ユーザーがqrusadorzでレポジトリがLearn-moduleでブランチがdevelopの例となっている。

npm i qrusadorz/Learn-module#develop

公式ドキュメントの書式は以下の通り。
https://docs.npmjs.com/cli-commands/install.html

npm install <githubname>/<githubrepo>[#<commit-ish>]:

上記は簡略系で、バージョン指定や後述のプライベートレポジトリ指定の場合にはurlの形式を利用する。

<protocol>://[<user>[:<password>]@]<hostname>[:<port>][:][/]<path>[#<commit-ish> | #semver:<semver>]

ちなみに指定のブランチにpackage.jsonがないとエラーになる。
エラー内容から判断するのは難しかった記憶があるため、必ず指定先に有効なpackage.jsonがあることを確認したい。

プロジェクトからアンインストールする
npm uninstall learn-module

アンインストールではGitHubのUrl指定ではなく、package.jsonに記述されているパッケージ名であることに注意。

GitHubを使ってプライベートレポジトリを共有する

プライベートレポジトリの場合、当然ですがURLを知っていても参照することはできません。
そのため、npm iではパブリックレポジトリとは書式が異なってきます。

プライベートレポジトリを参照するため、GitHubのアクセストークンを生成します。
https://help.github.com/ja/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line

権限はread:packagesrepoが必要になります。
https://help.github.com/en/github/managing-packages-with-github-packages/about-github-packages#about-tokens

生成したトークンをどうやってnpm iに指定するかは、古いですがGitHubのブログに載っています。
https://github.blog/2012-09-21-easier-builds-and-deployments-using-git-over-https-and-oauth/

git clone https://<token>@github.com/owner/repo.git
または
git clone https://<token>:x-oauth-basic@github.com/owner/repo.git

これを踏まえて、npm iではこのように指定できます。
例では、tokenがxxxxxxxxx、ユーザーがqrusadorz、レポジトリがlearn-private-module、ブランチがdevelopの例となっている。

npm i https://xxxxxxxxx@github.com/qrusadorz/learn-private-module#develop

これでプライベートレポジトリでもGCFにインストールできるようになります。
注意点として、残念なことにGitHubのトークンはレポジトリ単位ではなくユーザー単位で有効のため、package.jsonに直接記述して漏洩した場合、ユーザーの他のレポジトリが見られるリスクが伴います。
私は試せませんが、git configを利用した回避方法があるようなので、必要に応じて採用を検討してください。
https://stackoverflow.com/questions/23210437/npm-install-private-github-repositories-by-dependency-in-package-json

Conclusion

JavaScriptとNode.jsでソース共有の手段として、コピペとパッケージ化で調査してみました。

コピペの方はesmを使ったものを掘り下げていないため、後日機会があれば更新するかもしれません。
まとめの段階で、この程度の差ならテキストの置換でいけるよなと思ったり:joy:
基本的にコピペは保守性の低さから嫌われるため、コピペのメリットが大きい場合にのみ使用してください。(チームプレイでバグ修正時にコピペみるとしんどい:cry:

パッケージ化の方は実用性が高いため、十分に掘り下げました。
GitHubにより手軽な共有ができることで、受け入れやすいものになったと思います。
FaaSではトークンを隠せなそうなので完全解決とはなりませんでしたが、機密情報を一切含んでいないならトークンの使い捨てでしのげると思います。
パッケージにしてしまうといちいち更新してやらないといけないのかと思いましたが、@103ma2さんが記事にされているようnpm linkを使ってシンボリックリンクを張るとこれを回避できそうです。
https://qiita.com/103ma2/items/284b3f00948121f23ee4

Appendices

パッケージ化で作成したパブリックレポジトリ
https://github.com/qrusadorz/Learn-module

References

本体に全て掲載

既存のnpmをnpm audit fixエラーを出さずにアップデートする方法

$
0
0

はじめに

gulpを使用していて、npm audit fixしてください!ってエラーが表示される事が数回ありました。
これはパッケージに脆弱性のある箇所を自動修正してくれるコマンドらしく、これを入力する事で大半は通常通りに使用する事ができるようになります。(ダメな時もあるらしいですが私は通過しました)
ただ、エラーが出るのが苦手なので、どうすれば良いだろうな〜って漁っていたら、解決策を見つけました。

考え方

Q.なぜこのエラーが起こるのか
A.package.jsonのに入っているパッケージのバージョンが古いから

Q.どうすれば良いか
A.package.jsonに記載されてるバージョンが古くないかチェック
→package.json更新
→npmアップデート

意外と簡単に出来ちゃいます。

このアップデートで使用するコマンドが

npm install npm-check-updates

なのですが、具体的にどう言った時に使用すれば良いのか、以下に簡単に説明します。

できる事

  • package.jsonに書かれているnpmのバージョンを、一括で最新に書き変える事ができる。
  • 実際に書き変える前に確認だけすることもできる。
  • ncuコマンドで書き換えが出来る。

どんなときに使えるのか?

  • 既存のpackage.jsonを使用して、同じnpmをそのまま使いたいけど、バージョンだけは新しくしたいとき。

使い方

初回はnpm-check-updatesをグローバルにインストールします。

npm install -g npm-check-updates

上記のコマンドを入力すると、ncuというコマンドが使用できるようになります。

npx ncu -u

するとnpm outdatedの簡易版のような見た目で各パッケージの現在インストールされているバージョンと最新バージョンの情報がそれぞれ一覧表示されます。

gulpfile.js
gulp-babel^8.0.0-beta.2^8.0.0gulp-postcss^7.0.1^8.0.0gulp-sass^3.2.1^4.0.2postcss-flexbugs-fixes^3.3.0^4.1.0postcss-normalize-charset^1.1.1^4.0.

(ncuコマンドだけでも上記の処理が可能との記事もありましたが、私は出来ませんでした。)

そして、npmのアップデートをします。

npm update

最後にnpmのインストールをしたら終了です。

npm install 

※特定のパッケージのみアップデートを実行したい場合は

ncu -u {パッケージ名}

※特定のパッケージ以外をアップデートを実行したい場合は

ncu -x {パッケージ名}

で出来るそうです◎!
複数のパッケージがある場合は、パッケージごとを半角スペースで区切れば一気に処理ができます。

さいごに

時間に余裕があるときは、packeage.jsonは毎回新規で作ってあげた方が良いみたい。

参考サイト

脆弱性の警告を受けたnpmパッケージの依存関係を力技で直す : https://qiita.com/hibikikudo/items/0af352acac85fce28ec2
npm で作られたプロジェクトの依存モジュールを更新する : https://takuya-1st.hatenablog.jp/entry/2019/08/23/235613

YarnV2 こと Yarn@berry を試してみている

$
0
0

image

Yarn とは

yarn とは雑には alternative npm。高速な Node.js 向けのパッケージマネージャ。
package.json を解釈し、高速にパッケージの依存解決を行ってくれる。

Yarn V2 のインストール方法

Yarn v2 は現状 yarn@berryとしてバージョンを指定することでインストール可能。

how-to-install.sh
# インストール済みの場合はまずアンインストールする
npm uninstall -g yarn

# v2 を使いたければタグを明示してインストールする
npm install-g yarn@berry

V1 と V2 の違い

一次情報はこちらのエントリを参照のこと。スクショは公式から引用。
https://dev.to/arcanis/introducing-yarn-2-4eh1

個人的に特に気になっているものに 🍎 (赤いリンゴ) をつけておく。
色々途中なので変化したり間違ってる部分はご容赦ください。

🍏 CLI出力の改善

yarn v1 では絵文字が使われて可愛らしい出力だったが、v2 からは処理の具体的な内容を表示するように変化した。
色はアクセシビリティの関係でまだ変化するかもしれないらしい。
Image
出力には YN***** という番号が割り当てられ、この番号をもとに出力自体の情報を参照できるようになる、、、のかもしれない。

🍏 ワークスペースに対応するCLI

ナンノコッチャという感じであるが、ワークスペース内でのパッケージの再利用を提案してくれる様子。
個人的には新しいやつを使っていきたいので解決済みの依存から引き出して古いバージョンを使うみたいなことはしないと思うが、容量を節約したいシーンには役に立つだろう。これはオプショナルな挙動になるようだ。
Image

🍎 ゼロインストールへの対応

おそらく目玉機能。なんと実行しようとするスクリプトから依存を解決しつつ実行するという機能のようす。詳しい解説は https://next.yarnpkg.com/features/zero-installs参照のこと。
この機能のためか、yarn addしても node_modules ディレクトリは生成されない。従来の node_modules と同じ動きをさせたい場合は、別途設定を必要とする。

🍎 コマンド: yarn dlx (npx 相当のコマンド追加)

npmではパッケージの取得と実行を同時にこなす npxというコマンドが標準で提供されている。
yarnにも今回同様の動きをする yarn dlxが追加された。語源は download and executeらしい。
npxとのスタンスの違いとしては 取得と実行を同時にこなすところは変化しないとして、リモートスクリプトの実行には yarn dlxを使い、ローカルスクリプトの実行には yarn runを使うという使い分けを行うそうだ。曰く、タイプミスでの意図しないスクリプト実行を起こす危険性があるということらしい。

🍏 コマンド: yarn workspace foreach の追加

Yarn にワークスペースの概念が追加されたことによる機能の追加。
複数のリポジトリに対してコマンドを実行する場合外部コマンドを利用する必要があったが、このコマンドを利用すると yarn で制御を完結できるということらしい。細かくパッケージを分割して運用してるケースなどには便利なのかもしれない。@something/hogeみたいなパッケージ群で構成されるライブラリとかではコマンドの形式を揃えておくと役に立ちそう。

🍏 新しいプロトコル: patch:

特定の依存に対してオレオレパッチを適用しつつパッチを当てる必要がある場合に利用できる。
従来パッケージインストール後に node_modules のスクリプトを魔改造するようなシーンもあったかもしれない。(本当はしないほうが良いとは思うが古の不思議な力によってそうなってしまっていることもあるだろう)
で、今回 yarn の動き的に基本的には node_modules を生成しないようになっているので、そういうやり方がそもそもできなくなっている。そういうときに役に立つのがこの patch を指定したインストール方法。

sample-path-protocol.json
{"dependencies":{"left-pad":"patch:left-pad@1.3.0#./my-patch.patch"}}

これで left-pad に my-patch.patch の差分を適用した状態でパッケージがインストールされる。

🍏 新しいプロトコル: portal:

特定のディレクトリに対するシンボリックリンクを生成してくれる link:と似た動きをしてくれるらしい。ちなみに link:の場合は、任意のローカルディスク上のディレクトリへのシンボリックリンクを生成する。portal の場合は、逆にパッケージの任意のディレクトリへのシンボリックリンクを作成するということらしい。jQuery を yarn で持ってきて js だけコピーとかしなくて良くなるみたいな感じだろうか…?

🍏 ワークスペースを考慮しつつのリリース

大規模プロジェクト向け機能らしい。Lerna というワークフローをサポートするパッケージを例に上げているが、それに似た機能が標準でついてくるということのようだ。個人的にはお世話になるシーンがなさそうなので省略。

🍏 ワークスペースの制約

複数ワークスペースの依存を解決するための機能。同じく使わなさそうなので省略。
一部話題に上がっていた Prolog が使われている箇所というのがここの定義。

🍏 ビルド依存関係の追跡

ネイティブビルドが発生するパッケージの再ビルドが必要なビルドパスを検知し、最小限を実行する仕組み。Yarn v1 では問題になっていたそうだが、そこまで問題になったことはあんまりないなぁという印象。
大規模なプロジェクトになるとそういう問題に当たりがちなのかもしれない。

🍏 パッケージごとのビルド構成

dependencisMeta という定義を package.json に加えることで、パッケージのビルドを行うか行わないかみたいなスイッチができるようになる。デフォルトだと全部実行する。めったに使うシーンなさそう。

🍎 正規化された実行環境

従来、特に Windows で package.json の scripts を実行したときには bash で実行されないので環境変数の指定などで動かないなどクロスプラットフォーム開発上の問題が起こっていた。

cannot-run-on-windows.json
{"scripts":{"redirect":"node ./something.js > hello.md","no-cross-env":"NODE_ENV=prod webpack"}}

Yarn2 では npm scripts の実行に独自のインタプリタが導入されておりそういう問題にはまらないように設計されているということらしい。WSLも何もなくて Windows上に Node.js と yarn V2 をインストールしている場合はポータビリティが上がって良さそう。任意の引数の展開などにも対応する。
便利な一方で、 npm にはそんな機能はないので当然ながらこの機能を使用するとnpm との互換性がなくなる。

yarn-scripts-sample.json
{"scripts":{"lint-and-build":"yarn lint \"$@\"&& yarn build \"$@\""}}

🍏 peerDependencies の依存解決の改善

peerDependencies がうまく動かない場合があったのが改善されたらしい。
Yarn v2 を使うことで Plug'n'Play が十全に機能するためということらしい。

🍎 ロックファイルのフォーマット変更

はい、破壊的変更です。yarn.lockは従来 YAML で構成されていたが、これが syml という亜種になり機能が限定された代わりに高速になったらしい。中身もだいぶ変化している。yarn V1 と V2 の間で yarn.lock に互換性はないとみたほうが良さそう。

🍎 TypeScript に実装が変わりました

使う文には影響ないけど実装が TypeScript になりました いうこと。

🍏 モジュラーアーキテクチャの採用

yarn 自体をプラグインで拡張できるようになったそうな。TypeScript への移行はこのプラグインを書くときに補完が効いたりとかで便利になったということなのだろう。型定義は @types/yarnで取得できるらしい。

🍏 正規化された設定

yarn V1 では npm との互換性を重視したために yarn の設定はどこに書けばいいのかわからないというフィードバックが多数寄せられたとのことで、yarn V2 からは .yarnrc.ymlというファイルに書けばOKとなったとのこと。V1にも.yarnrcってあるじゃん?とは思うけどそういうことではないのかもしれない・・・。V1で .yarnrcを必要とする使い方をしていた場合は移行が必要になる。
https://legacy.yarnpkg.com/lang/ja/docs/yarnrc/

🍎 厳密なパッケージ管理

大きな変化の1つ。package.json の depencencies に定義されていないパッケージは yarn V2 で依存を解決し、実行するコードにおいては利用できないようになった。Plug'n'Play の依存解決によるものらしい。よってなんらかの依存の依存によってインストールされているパッケージを利用していた場合は明示的にインストールが必要になる。

🍏 bundleDependencies の廃止

こういうのもあったのねという感じ。使ったことがなかったので省略。
移行ガイドを参照して代替え手段を見つけるべし。

🍎 パッケージは読み取り専用に

パッケージの依存解決の際に基本的に node_modules に展開しなくなったので、必然的にパッケージの魔改造ができなくなった。どうしてもその必要がある場合は、patch: プロトコルを使用するか、あるいは従来どおりに展開してもらえるよう設定を行う必要がある。

いくつかやってみた

拡張された npm-scripts

yarn 独自のインタプリタを活用するとこんなふうなスクリプトを実行できる

double-echo-package.json
{"scripts":{"double-echo":"echo \"$@\"; echo \"$@\""}}

yarn での実行結果はこのようになる

result-double-echo-yarn.sh
$ yarn run double-echo hogehoge
hogehoge
hogehoge

npm で実行してしまうとこうなる。

result-double-echo-npm.sh
$ npm run double-echo hogehoge
>echo"$@";echo"$@""hogehoge"
hogehoge

yarn と npm の完全な互換性を期待して yarn と npm を混在させていた場合は、こういう違いが出てくるので気をつける必要がある。

node コマンドでのスクリプトの実行

yarn V2 で依存を解決している状態の場合、Node.js 自身でパッケージを参照できなくなる。なんてこったい。
そのため、代替として yarn nodeを実行する必要がある。こうすることで yarn で独自に解決した依存を利用できるということのようだ。
下記は yarn V2 で依存を解決したあとのフォルダツリー。依存として chalk を追加したあとの状態のもの。見てわかると思うが慣れ親しんだ node_modules が存在しない。代わりに .yarn/cacheというディレクトリができていてそこに zip が複数配置されている。スクリプト実行時に必要になったタイミングでこの zip を展開して実行しているということのようだ。
Image

V1 から V2 への移行方法は?

https://yarnpkg.com/advanced/migrationを見るべし。
ちなみに手元のアプリを何もせずそのまま yarn.lock を吹き飛ばして再構築したところ動かなかったので、がんばりがそこそこ必要になりそうだ。別途やったことはまとめるかもしれない。

まとめ

Yarn V2 は ZeroInstall と Plug'n'Play をベースにしたパッケージマネージャに生まれ変わり、単なる高速な npm との互換性を維持したツールと言う立ち位置を卒業するようす。 dev.to の記事の書き方からもそういう雰囲気を感じ取れた。
高速な npm を求めていた層からは、ちょっとそれどうなのという感じもなくはないようで、例えばこのIssueでは大幅な変化があったことからそのまま yarn の更新としてリリースするのはどうなのか?みたいな議論がされている。しかしながら基本的には yarnの正当な後継としてリリースされるようだ。
もとの node_modules の仕組みも考えさせられる部分はあると思うので現状が最高というわけでも多分ないのでそういうのをまとめてひっくるめていい方向に進むきっかけに yarnV2 がなってくれるといいなと感じた。

from Scrapbox

Huskyを入れてもgitのhookが動かない

$
0
0

husky@4.2.1 + lint-stagedでpre commit時にprettierを動かそうとしたんだけどうまく動かなかった。

$ ls .git/hooks

してみてもhookが生成された形跡なし。

結論からいうと ローカルのgitバージョンとhuskyのバージョンの問題でした。

husky@4.2.1の対応gitバージョンは

Verify that your version of Git is >=2.13.0.

ということで2.13.0以上。
対して自分のPCのローカルgitバージョンは2.11.0。

単純にローカルgitバージョンを上げれば対応できそうだが、チーム開発だと全員のgitバージョン揃えるのもめんどそうなので(gitはそんな頻繁にバージョン上げないだろうし)低いgitバージョンでも動かせるように対応。

$ yarn add -D husky@2.7.0

これで無事hookが生成されgit@2.11.0でも動くようになりました。

簡単に投票を共有できるツールをOSSで公開したよ

$
0
0

何に使うためにつくったの?

チームのメンバーでブログのテーマを決めるため(結局間に合わなかったけど)

URLは?

https://syukei-san.xyz/

使い方は?

  1. 投票したい/多数決を取りたいデータを1行ごとに入力
  2. 利用規約を読んで同意する
  3. 出来上がったフォームをメンバーにシェアする
  4. メンバーがフォームで投票する(一人が複数投票することはできません!)

どの言語で作ったの?

nodejs

github URLは?

https://github.com/newsdict/syukei-san.xyz/

なんでテスト書いてないの?

急いで作ったのでテスト書いてないです。。。
もっと使う用途が出てきたら書いていこうと思います。

仕様を考えながら作る場合、テストコードどうしてるんだろう?

いつ公開しはじめたの?

7 Jul 2019

nestjsで.envを利用できるようにする。ついでにdockerも

$
0
0

nestjsで.envを利用できるようにする。ついでにdockerも

Table of Contents
  1. configModuleを追加する
  2. .envを作成
  3. nestjsに.envの設定を読み込ませる
  4. dockerに.envを適応する

1.configModuleを追加する

nestjsではdot-envを拡張した@nestjs/configがあります。

上記を追加します。

$npm i --save @nestjs/config

プロジェクトディレクトリにあるapp.moduleにconfigの読み込みを追加します。

src/app.module.ts
import{Module}from'@nestjs/common';import{ConfigModule}from'@nestjs/config'; ##ここから追記import{AppController}from'./app.controller';import{AppService}from'./app.service';@Module({imports:[##ここから追記ConfigModule.forRoot({}),##ここまで追記],controllers:[AppController],providers:[AppService],})exportclassAppModule{}

これで、読み込みは完了です。
次に .env を作成していきます。

2. .envを作成

.envをproject directoryの下に追加していきます。
サンプルでは、dbの設定とportを追記します。

PORT=3000
NODE_ENV=develop

DB_PORT=3306
DB_USERNAME=develop
DB_PASSWORD=password
DB_DATABASE=develop
DB_HOST=db-server

先ほど追加したapp.module.tsに簡易的によびこませます。

src/app.module.ts
import{Module}from'@nestjs/common';import{ConfigModule}from'@nestjs/config';import{AppController}from'./app.controller';import{AppService}from'./app.service';@Module({imports:[ConfigModule.forRoot({##ここから追記isGlobal:true,envFilePath:'.env'##ここまで追記}),],controllers:[AppController],providers:[AppService],})exportclassAppModule{}

これで読み込みは完了しました。
ついでにenvに読み込ませた、PORTの設定をmain.tsに追記します。
configModuleを追加すると、configServiceを利用することができます。

src/main.ts
import{NestFactory}from'@nestjs/core';import{AppModule}from'./app.module';import{ConfigService}from'@nestjs/config';##ここを追記asyncfunctionbootstrap(){constapp=awaitNestFactory.create(AppModule);##ここから追記constconfigService=app.get(ConfigService);awaitapp.listen(configService.get('PORT')||3000);##ここまで追記}bootstrap();

以上です。
ここまで十分開発はそこそこできるのですが、
TypeOrmModuleの設定(webpack hotreloadingだとentity読み込まない)と環境別に設定を分けるための設定を保村していきます。

3. nestjsに.envの設定を読み込ませる

現状は以下のようなディレクトリで、TypeormModuleを利用していることを想定しています。

.
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── entity
│   └── user.entity.ts
└── main.ts

設定用のconfig.tsを作成src/config以下に設定します。

$mkdir cofig
$cd config
$touch configuration.ts

作成したconfig/configuration.tsに設定ファイルを読み込ませるように設定を記載します。

config/configuration.ts
exportdefault()=>({port:parseInt(process.env.PORT,10)||3000,database:{type:'mysql',host:process.env.DB_HOST,port:process.env.DB_PORT,username:process.env.DB_USERNAME,password:process.env.DB_PASSWORD,database:process.env.DB_DATABASE,entities:['src/**/**.entity{.ts,.js}'],synchronize:process.env.NODE_ENV==='develop'?true:false,},});

次に設定したconfigurationファイルを読み込ませるようにsrc/app.module.tsに読み込ませます。

src/app.module.ts
import{Module}from'@nestjs/common';importconfigurationfrom'./config/configuration';import{TypeOrmModule}from'@nestjs/typeorm';import{ConfigModule,ConfigService}from'@nestjs/config';import{AppController}from'./app.controller';import{AppService}from'./app.service';@Module({imports:[ConfigModule.forRoot({##ここから追記load:[configuration],isGlobal:true,##ここまで追記}),TypeOrmModule.forRoot({...}),],controllers:[AppController],providers:[AppService],})exportclassAppModule{}

load を追加して記載したファイルを読み込ませることができます。
次に、TypeormModuleのdevelop環境の設定をしていきます。
configuration.tsとapp.moudule.tsに設定を追記していきます。

src/config/configuration.ts
importentitiesfrom'../entity';// [ ...Entities ]を想定しています。レポ見てもらうとわかるかもexportdefault()=>({port:parseInt(process.env.PORT,10)||3000,database:{type:'mysql',host:process.env.DB_HOST,port:process.env.DB_PORT,username:process.env.DB_USERNAME,password:process.env.DB_PASSWORD,database:process.env.DB_DATABASE,entities:process.env.NODE_ENV==='develop'?entities:['src/**/**.entity{.ts,.js}'],//webpack hot reloadingでも読み込むようにinjectします。synchronize:process.env.NODE_ENV==='develop'?true:false,},});

app.module.tsのTypeormModuleに設定を変更していきます。

src/app.module.ts
import{Module}from'@nestjs/common';importconfigurationfrom'./config/configuration';import{TypeOrmModule}from'@nestjs/typeorm';import{ConfigModule,ConfigService}from'@nestjs/config';import{AppController}from'./app.controller';import{AppService}from'./app.service';@Module({imports:[ConfigModule.forRoot({load:[configuration],isGlobal:true,}),##ここから編集TypeOrmModule.forRootAsync({//forRootではなく、forRootAsyncimports:[ConfigModule],inject:[ConfigService],useFactory:(configService:ConfigService)=>({...configService.get('database'),}),}),##ここまで編集],controllers:[AppController],providers:[AppService],})exportclassAppModule{}

以上でwebpackhot-reloadingの時でも読み込むことができます。

4. dockerに.envを適応する

以下のようにdocker-compoesファイルに直書きしたことを想定しています。
db-serverに、envfileの読みこみとenvironmentの設定をしていきます。

docker-compose.yml(旧)
version: '3.7'
services:
  api-server:
    container_name: api-server
    build: .
    tty: true
    restart: always
    ports:
      - '3000:3000'
    volumes:
      - type: bind
        source: .
        target: /api-server
    depends_on:
      - db-server
  db-server:
    container_name: db-server
    image: mysql:5.7.29
    restart: always
    ports:
      - '3306:3306'
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: develop
      MYSQL_USER: develop
      MYSQL_PASSWORD: password
docker-compose.yml
version:'3.7'services:api-server:container_name:api-serverbuild:.tty:truerestart:alwaysports:-'3000:3000'volumes:-type:bindsource:.target:/api-serverdepends_on:-db-serverdb-server:container_name:db-serverimage:mysql:5.7.29restart:alwaysports:-'3306:3306'## ここから追記env_file:-.envenvironment:MYSQL_ROOT_PASSWORD:'${DB_PASSWORD}'MYSQL_DATABASE:'${DB_DATABASE}'MYSQL_USER:'${DB_USERNAME}'MYSQL_PASSWORD:'${DB_PASSWORD}'## ここまで追記

以上で設定は完了です。
レポ

electron-storeがパッケージ化したら動かねぇ!

$
0
0

解決方法はとっても単純でした.

package.jsonのdependencieselectron-storeを追加するだけ.

{(省略)"devDependencies":{"electron":"^7.1.10","electron-builder":"^22.3.2","electron-store":"^5.1.0"},"dependencies":{"electron-store":"^5.1.0"}}

dependenciesがない場合は作ってください.


Laravel mixで環境変数を.env以外のファイルから読み込む

$
0
0

ステージング、本番サーバーにElasticbeanstalkを使っていて、ハマったのでメモ。

結論

https://laravel-mix.com/extensions/env-file
を使う。

// package.json
"scripts": {
        "local": "npm run development", // localは.envを読めばいいので上記使う必要なし
        "dev": "NODE_ENV=development ENV_FILE=./env/.env.staging node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", // デフォルトから変更。
        "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch": "npm run development -- --watch",
        "watch-poll": "npm run watch -- --watch-poll",
        "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
        "prod": "npm run production",
        "production": "cross-env NODE_ENV=production ENV_FILE=./env/.env.production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
    },
// webpack.mix.js
let mix = require('laravel-mix');
require('mix-env-file');

if(process.env.ENV_FILE) {
    mix.env(process.env.ENV_FILE);
}}

[初学者向け]AzureFunctions(TimerTrigger)+nodejsをはじめてみよう

$
0
0

はじめに

社内勉強用のコンテンツをそのまま公開しました

Azure Functionsで処理を書きたいんだけど何から勉強すればいいんだろう・・・という方向けに、AzureFunctionsのTimerTriggerを使ってURL監視するサービスを一緒に作っていきたいと思います。

こんなことを勉強していきます

  • 必要なソフトウェアのインストール
  • nodejsの超基本中のキホン
  • AzureFunctionsへのデプロイ
  • AzureFunctionsをローカルで動かしてみよう
  • モジュールの活用
  • AzureFunctionsにおける環境変数の活用
  • 学んだことを使ってURL監視サービス(証明書チェック機能付き)を作ってみる

じゃあ早速やっていきましょう

必要なもの

  • Azureアカウント

必要なもののインストール

nodejs(LTS)のインストール

yarn(パッケージマネージャ)のインストール

Azure cliのインストール

Azure Functions Core Toolsのインストール

必要に応じてインストール

お好みのエディタ
私のお好みはIntelliJですが、VisualStudioCodeは割ととっつきやすいので使ってみてください。

まずはnodejsの基本中のキホンから

azure-functions-samplesというディレクトリを作ってその中にindex.jsを作って実行してみましょう

index.js
functionhello(){console.log("test");}hello();

実行してみる

node index.js

yarnで実行してみよう

yarn initコマンドを実行することでpackage.jsonのひな型を作ります。とりあえずは何も入力しなくても大丈夫です。

azure-functions-samples> yarn init
yarn init v1.21.1
question name (fetch-timer):
question version (1.0.0):
question description:
question entry point (index.js):
question repository url:
question author:
question license (MIT):
question private:
success Saved package.json
Done in 13.50s.
  • package.jsonではライブラリの依存関係を記述したりするのですが、それは後で説明するとして、package.jsonのscriptsの部分に書けば好きなコマンドを実行できます。
package.json
{"name":"simple-timer-function","version":"1.0.0","description":"","scripts":{"start":"node index.js"},"author":"","main":"index.js","license":"MIT"}

package.jsonのscriptsの部分を書いておくことでyarn startすると実行することができる。

まぁここまでは大丈夫ですよね。

AzureFunctionsについて

AzureFunctionsにはいろいろな機能があります。

  • HTTPアクセスを受け取って処理を実行する
  • CosmosDBにデータが入ったらTriggerとして起動する
  • BlobStorageにデータが格納されたら処理を実行する
  • 定期的に実行する

また、AWSLambdaのように必要に応じて随時動かしておくこともできますし、常時動かしておくこともできます。
AWS Lambdaでは随時動かした場合、初回起動に時間がかかったりしますが、これはAzureFunctionsでも同様です。許容できない場合は定期的にポーリングしておくことや常時起動にしておくことで回避します。

今回は、定期的にURLを監視したいのでTimerTrigger(定期的に実行する)を使っていきます。

シンプルなTimerTrigger

simple-timerディレクトリを作ります。

$ mkdir simple-timer

timerディレクトリの下でfunctionのひな型を作ります。

  • 言語はJavaScriptを選択してください
azure-functions-samples\simple-timer> func new
Select a language: Starting from 2.0.1-beta.26 it's required to set a language for your project in your settings
'node' has been set in your local.settings.json
Select a template: Timer trigger
Function name: [TimerTrigger] simple-timer
Writing C:\Users\uzres\git\azure-functions-samples\simple-timer\simple-timer\index.js
Writing C:\Users\uzres\git\azure-functions-samples\simple-timer\simple-timer\readme.md
Writing C:\Users\uzres\git\azure-functions-samples\simple-timer\simple-timer\function.json
The function "simple-timer" was created successfully from the "Timer trigger" template.

せっかちなので10秒に1回起動するようにしましょう

function.json
{"bindings":[{"name":"myTimer","type":"timerTrigger","direction":"in","schedule":"*/10 * * * * *"}]}

デプロイ

CloudShellを起動します。

image.png

CloudShellが起動出来たらFunctionsを作っていきます。
<適当な文字列>にはuzreskなど自身のものとわかるような名前にしておきましょう。

export RESOURCE_SUFFIX=<適当な文字列>

export RG_NAME=rg-$RESOURCE_SUFFIXexport STORAGE_NAME=azfunsamples$RESOURCE_SUFFIXexport FUNCTION_NAME=azfun-samples-$RESOURCE_SUFFIXexport LOCATION=japaneast

az group create --name$RG_NAME--location$LOCATION

az storage account create \--name$STORAGE_NAME\--location$LOCATION\--resource-group$RG_NAME\--sku Standard_LRS

az functionapp create \--name$FUNCTION_NAME\--storage-account$STORAGE_NAME\--consumption-plan-location$LOCATION\--resource-group$RG_NAME

FunctionsができたらAzureの画面で確認しておきます。

image.png

今度はローカルに戻ってデプロイしていきます。

azure-functions-samples\simple-timer> func azure functionapp publish azfun-samples-xxxxxxx
Getting site publishing info...
Creating archive for current directory...
Uploading 1.49 KB [###############################################################################]
Upload completed successfully.
Deployment completed successfully.
Syncing triggers...
Syncing triggers...
Functions in azfun-samples-xxxxxxx:
    simple-timer - [timerTrigger]

Azureのポータルに戻り、関数を選択して「統合」というところを見ると変更したスケジュールが反映されているのがわかると思います。(少し薄いです)

image.png

「監視」を見ると10秒おきに起動していることと、さらに進むと出力したログが出ていることを確認できます。

image.png

ローカルで実行する

毎回デプロイしているのは時間の無駄なのでローカルでAzureFunctionsを実行してみましょう。
ローカルで動かすための設定はlocal.settings.jsonに記載します

local.settings.json
{"IsEncrypted":true,"Values":{"FUNCTIONS_WORKER_RUNTIME":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"},"ConnectionStrings":{}}

local.settings.jsonに何を書けばいいのかは実は考える必要はありません。
コマンドを一発実行するだけでサーバから設定を取得することができるのです。

func azure functionapp fetch-app-settings <functionAppName>

実行するとlocal.settings.jsonが以下のように変更されるはずです。

local.settings.json
{"IsEncrypted":true,"Values":{"FUNCTIONS_WORKER_RUNTIME":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","FUNCTIONS_EXTENSION_VERSION":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","AzureWebJobsStorage":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","AzureWebJobsDashboard":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","WEBSITE_NODE_DEFAULT_VERSION":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","WEBSITE_CONTENTAZUREFILECONNECTIONSTRING":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","WEBSITE_CONTENTSHARE":"","APPINSIGHTS_INSTRUMENTATIONKEY":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","WEBSITE_RUN_FROM_PACKAGE":"xxxxxxxxxxxxxxxxxxxxxxxxxxx"},"ConnectionStrings":{}}

それではローカル環境で実行してみましょう。
VSCodeのターミナルを開いて以下のコマンドを実行します。

func start

10秒間隔で実行にしていた場合には下記のようにいつ動くのかが書かれたメッセージの後に、時間が来たら実行結果が表示されているはずです。

[2020/01/24 4:27:44] The next 5 occurrences of the 'simple-timer' schedule (Cron: '0,10,20,30,40,50 * * * * *') will be:
[2020/01/24 4:27:44] 01/24/2020 13:27:50+09:00 (01/24/2020 04:27:50Z)
[2020/01/24 4:27:44] 01/24/2020 13:28:00+09:00 (01/24/2020 04:28:00Z)
[2020/01/24 4:27:44] 01/24/2020 13:28:10+09:00 (01/24/2020 04:28:10Z)
[2020/01/24 4:27:44] 01/24/2020 13:28:20+09:00 (01/24/2020 04:28:20Z)
[2020/01/24 4:27:44] 01/24/2020 13:28:30+09:00 (01/24/2020 04:28:30Z)

モジュールを追加する

先ほどはひな型をそのまま動かしてみましたが今度は外部のリソースにHTTPSアクセスし、それをログに出力してみます。
外部のリソースにHTTPSアクセスするコードを一から頑張って自分で書く必要はありません。ライブラリをローカルにインストールして、そのライブラリを使ってHTTPSアクセスしてみましょう。

azure-functions-samplesにfetch-timerというディレクトリを作成します。

先ほどと同じようにfunctionのひな型を作りましょう

PS C:\Users\uzres\git\azure-functions-samples\fetch-timer> func new
'node' has been set in your local.settings.json
Select a template: Timer trigger
Function name: [TimerTrigger] fetch-timer
Writing C:\Users\uzres\git\azure-functions-samples\fetch-timer\fetch-timer\index.js
Writing C:\Users\uzres\git\azure-functions-samples\fetch-timer\fetch-timer\readme.md
Writing C:\Users\uzres\git\azure-functions-samples\fetch-timer\fetch-timer\function.json
The function "fetch-timer" was created successfully from the "Timer trigger" template.

yarn initしてpackage.jsonのひな型を作ってみましょう

package.jsonを確認してみましょう。

package.json
{"name":"fetch-timer","version":"1.0.0","main":"index.js","license":"MIT",}

http/httpsアクセスするモジュールaxiosをインストールします。

PS C:\Users\uzres\git\azure-functions-samples\fetch-timer> yarn add axios
・・・
Done in 0.58s.

再度package.jsonを確認するとaxiosが追加されていることがわかります。

package.json
{"name":"fetch-timer","version":"1.0.0","main":"index.js","license":"MIT","dependencies":{"axios":"^0.19.2"}}

また、node_modulesというディレクトリができていて、その中にライブラリが入っていることが確認できると思います。
package.jsonにはライブラリの依存関係が書かれており、package.jsonがある状態でyarn initすると必要なライブラリが自動でインストールされるようになっています。
yarn add xxxxとすることでpackage.jsonに自動で書き込まれますが、パッケージのバージョンを固めたりするために手で編集することもあります。

とりあえずpackage.jsonのパッケージで気をつけておきたいバージョン記法のキャレット(^)とチルダ(~)的なお話を読んでふーん。くらいで覚えておきましょう。

HTTPSアクセスしてみよう

Google Books APIにアクセスして「Azure」がタイトルにつくデータを取得してみます。

こちらにアクセスしてみましょう。

https://www.googleapis.com/books/v1/volumes?q=Azure

qの後ろに検索する文字列を入れることで書籍のデータをjson形式で取得することができます。

データにアクセスして、ステータスコードとjsonデータを取得するコードを書いてみます。
(コピペしないで確認しながら写経しましょう)

index.js
constaxios=require('axios');module.exports=asyncfunction(context,myTimer){constresult=awaitaxios(`https://www.googleapis.com/books/v1/volumes?q=Azure`).then(response=>{context.log('status code: ',response.status);context.log('data',response.data);})};

ローカルで実行して問題なければAzureにデプロイして実行してみましょう。

問題①

レスポンスのデータの中にはitemsという書籍のデータが入っている配列があります。
その数を出力してみてください。
答えは次の章にありますが、まずは見ないでやってみましょう。

タイトルだけ抽出してみる

JSONを解析してタイトルだけ表示してみましょう。for文を使ってくるくる回して表示してみます

index.js
constaxios=require('axios');module.exports=asyncfunction(context,myTimer){constsearchValue="Azure";constresult=awaitaxios(`https://www.googleapis.com/books/v1/volumes?q=${searchValue}`).then(response=>{constitems=response.data.items;for(i=0;i<items.length;i++){context.log(items[i].volumeInfo.title);}}).catch(error=>{context.log('error: '+error);});};

for文でもよいのですが、forEachを使う書き方もあります。

index.js
constaxios=require('axios');module.exports=asyncfunction(context,myTimer){constsearchValue="Azure";constresult=awaitaxios(`https://www.googleapis.com/books/v1/volumes?q=${searchValue}`).then(response=>{response.data.items.forEach(item=>{context.log(item.volumeInfo.title)})}).catch(error=>{context.log('error: '+error);});};

こんな感じで実行結果が出るはずです。

[2020/01/24 4:27:49] Host lock lease acquired by instance ID '0000000000000000000000009E8328FA'.
[2020/01/24 4:27:50] Executing 'Functions.fetch-timer' (Reason='Timer fired at 2020-01-24T13:27:50.0705362+09:00', Id=a5682498-bb5a-46b3-b3fd-78bea46c3de8)
[2020/01/24 4:27:51] status code:  200
[2020/01/24 4:27:51] Azureテクノロジ入門 2019
[2020/01/24 4:27:51] PowerShell for Azure
[2020/01/24 4:27:51] Azureテクノロジ入門 2018
[2020/01/24 4:27:51] ひと目でわかるAzure Active Directory 第2版
[2020/01/24 4:27:51] Microsoft Azure導入ガイドブック
[2020/01/24 4:27:51] Windows Azure実践クラウド・プログラミングfor C#/Visual Basic/PHP
[2020/01/24 4:27:51] Microsoft Azureへの招待
[2020/01/24 4:27:51] Azure定番システム設計・実装・運用ガイド
[2020/01/24 4:27:51] ひと目でわかるAzure 基本から学ぶサーバー&ネットワーク構築
[2020/01/24 4:27:51] Windows Azure APIリファレンス

Azure上にもデプロイして出力されることを確認しておきましょう。
ちなみにAzure上だとロケールの関係で日本語のタイトルの本が出てこないかもしれません。

エラーの時にログを出力する

接続先が間違っていた時には正常にデータがとれずエラーになるはずです。
エラーの場合にログが出力されるように記述してみましょう。
(接続先をわざとlocalhostに変更しています)

index.js
constaxios=require('axios');module.exports=asyncfunction(context,myTimer){constresult=awaitaxios(`https://localhost/books/v1/volumes?q=Azure`).then(response=>{context.log('status code: ',response.status);context.log('data',response.data);}).catch(error=>{context.log('error: '+error);});};

Azureポータル上でログを見るとエラーが出たときにcatchできていることが確認できていると思います。

2020-01-24T02:00:39.107 [Information] error: Error: connect EACCES 127.0.0.1:443

監視タブの一覧上ではグリーンのランプがついていたと思いますが、これはcatchだけして正常終了してしまっているからです。
異常終了させるにはcatchしたあとにthrowします。

index.js
constaxios=require('axios');module.exports=asyncfunction(context,myTimer){constsearchValue="Azure";// const result = await axios(`https://www.googleapis.com/books/v1/volumes?q=${searchValue}`)constresult=awaitaxios(`https://localhost/books/v1/volumes?q=${searchValue}`).then(response=>{response.data.items.forEach(item=>{context.log(item.volumeInfo.title)})}).catch(error=>{context.log('error: '+error);throwerror;});};

image.png

環境変数に切り出す

接続先のURLなどは環境変数に切り出すことで1つの関数で色々な役割をこなすことができますよね。
たとえばHTTPアクセスしてエラーにならないことを確認する関数を作った場合、URLの部分だけ差し替えることができれば再利用性が増しますよね。

index.js
constaxios=require('axios');module.exports=asyncfunction(context,myTimer){constresult=awaitaxios(`https://www.googleapis.com/books/v1/volume?q=Azure`).then(response=>{response.data.items.forEach(item=>{context.log(item.volumeInfo.title)})}).catch(error=>{context.log('[ERROR]: '+error);throwerror;});};

https://www.googleapis.com/books/v1/volume?q=Azureの部分を環境変数に切り出してみましょう。
環境変数はAzureポータル上で設定し、ソース上から環境変数を利用します。

関数アプリの設定をクリックします。

image.png

アプリケーション設定の管理をクリックします。

image.png

環境変数を設定します。ここではURLにhttps://www.googleapis.com/books/v1/volume?q=Azureを入力します。

image.png

最後に保存ボタンを押すのを忘れないようにしましょう

ソースを修正していきます。

  • 環境変数から値を取得するにはprocess.env.[環境変数]を使います。
  • 環境変数URLが設定されていない場合はエラーログを出力します。
index.js
constaxios=require('axios');module.exports=asyncfunction(context,myTimer){constURL=process.env.URL;if(typeofprocess.env.URL==='undefined'){context.log("[ERROR]URL is undefined.");}constresult=awaitaxios.get(URL).then(response=>{response.data.items.forEach(item=>{context.log(item.volumeInfo.title)})}).catch(error=>{context.log('[ERROR]: '+error);throwerror;});};

ソースを修正したらそのまま実行しましょう(func start)

このようなエラーが出るはずです。

[2020/01/24 5:42:50] [ERROR]URL is undefined.
[2020/01/24 5:42:50] URL: undefined

環境変数がされていないので出ているエラーですね。
ローカル環境で動かす手順で紹介しましたが、fetch-app-settingsは環境変数の情報を取得するコマンドなので、このコマンドを再度実行してみましょう

func azure functionapp fetch-app-settings azfun-samples-xxxxxxx

実行した結果、local.settings.jsonは以下のようにURLが追加されているはずです。

local.settings.json
{"IsEncrypted":true,"Values":{"FUNCTIONS_WORKER_RUNTIME":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","FUNCTIONS_EXTENSION_VERSION":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","AzureWebJobsStorage":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","AzureWebJobsDashboard":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","WEBSITE_NODE_DEFAULT_VERSION":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","WEBSITE_CONTENTAZUREFILECONNECTIONSTRING":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","WEBSITE_CONTENTSHARE":"","APPINSIGHTS_INSTRUMENTATIONKEY":"xxxxxxxxxxxxxxxxxxxxxxxxxxx","WEBSITE_RUN_FROM_PACKAGE":"xxxxxxxxxxxxxxxxxxxxxxxxxxx""URL":"xxxxxxxxxxxxxxxxxxxxxxx"},"ConnectionStrings":{}}

再度実行してローカル環境でうまく動くことを確認しましょう。
問題なければAzure上にデプロイして実行してみましょう。

問題②

証明書の有効期限が残り何日かを取得してみましょう
ヒント:ssl-checkerを使ってみよう

ログには以下が出力されるようにしてください

[INFO]SSL daysRemaining  <証明書有効期限の残り日数>

問題③

ここまで学んだことを使ってURL監視を実装してみよう

  • 対象URLにとある間隔でアクセスし以下を確認してください。
    • HTTP Responseのステータスコードが200であること
    • ResponseBodyに特定の文字列があること
      • BooksAPIを使うのであればtotalItemsという文字列があるかを確認してみてください。
    • 証明書の有効期限内であること
  • 対象URL、証明書の有効期限残り日数は環境変数で指定できるようにしましょう
  • エラーの場合は文字列の先頭に[ERROR]という文字を付与しましょう。

後片付け

リソースグループごとガツンと消しておきましょう。取り返しがつかないので間違えないように!!

az group delete --name <リソースグループ名>

おわりに

TimerFunctionsを使うことで指定された期間に不要なサービスを自動停止・開始することなども実装できるはずなので是非ともチャレンジしてみてください。

Puppeteerを使ってスクレイピングする時に個人的にはまったポイントのメモ

$
0
0

概要

Puppeteerを使用してヘッドレスブラウザとしてウェブスクレイピングを行う時に、個人的に詰まった点のメモです。

前提

Mac OS v10.15.2(19C57)
VSCode 1.41.1
node v12.14.1
Puppeteer 2.1.0

その1 コンソール出力ができない

問題

page.evaluateを使うことで、ページ読み込み後にブラウザ内で任意のスクリプトを実行してHTMLの解析などが可能ですが、デバッグ用にconsoleを仕込んでも何も表示されません。

// 初期化処理constbrowser=awaitpuppeteer.launch({headless:true,args:['--no-sandbox','--disable-setuid-sandbox']});constpage=awaitbrowser.newPage();// 指定のURLのページを開くawaitpage.goto('https://hoge.hoge.hoge');// ブラウザ内でスクリプトを実行constresult=awaitpage.evaluate(()=>{// ここでコンソールを出力しても何も表示されないconsole.log('eval start');// do something});

解決策

pageに対して、consoleイベントを予め登録し、イベント呼び出し処理内でconsoleで書き込みを行います。
page.evaluate内でconsoleを実行するとこのイベントが発火されてconsole出力がされるようになります。

constpage=awaitbrowser.newPage();// コンソールイベントを登録page.on('console',msg=>{for(leti=0;i<msg._args.length;++i)console.log(`${i}: ${msg._args[i]}`);});// 指定のURLのページを開くawaitpage.goto('https://hoge.hoge.hoge');// ブラウザ内でスクリプトを実行constresult=awaitpage.evaluate(()=>{console.log('eval start');// do something});

その2 querySelectorAllで取り出した結果が空のオブジェクトになる

問題

page.evaluate内でquerySelectorを使ってHTML要素を出力しようとするが、正しく値が取れない。

constresult=awaitpage.evaluate(()=>{constdataList=[];// hogeクラスタグ配下のaタグを全て取得constnodeList=document.querySelectorAll('.hoge > a');// 内部のhtmlを出力nodeList.forEach(_node=>{dataList.push(_node.innerText);});returndataList;});// 出力は空文字になる ["", "", ""]console.log(result);

解決策

querySelectorAllで取り出した値は配列になっていない為、Array.fromで配列に変換した後に列挙を行う。

constresult=awaitpage.evaluate(()=>{constdataList=[];// Array.fromで配列に変換constnodeList=Array.from(document.querySelectorAll('.hoge > a'));// 内部のhtmlを出力nodeList.forEach(_node=>{dataList.push(_node.innerText);});returndataList;});console.log(result);

最後に

個人的にPuppeteer使用中にはまった点を書きました。
Puppeteer自体は直感的で便利なのですが、思いがけないところではまって時間を使ってしまいしました。
(マニュアル等をしっかり読まず使えしまう事の弊害かもしれませんが。)

【Slack】OOOoO、キミにきめた!Bolt on Docker + Cloud Runでユーザーをランダムで選んでくれるSlash commandを作った話

$
0
0

こんにちは。
チーム内で週一で勉強会をやっているのですが、気付いたら3回に2回は僕がやってます。
「こりゃいかんぞ!Outputこそ最大の学びの場やぞ!」と思ったのですが、なんか自分から次はあなたねと指名するのもプレッシャー与えちゃうかも...と悩んでいました。

その仕事、Slackで。

ということで、Slackのslash commandに次の発表者を決めてもらうことにしました。
SlackにはBoltというnode.jsで動作するSlack app frameworkがあるので、今回はそれをDocker上で動作させ、最終的にはGCPのサーバーレスコンテナランタイムCloud Runでslash commandを提供するところまでやってみます。

アウトプットとしては、Slackのチャンネルで/kiminikimetaとコマンドを打つと、Botが「@xxxxx、キミに決めた」とそのチャンネルのユーザーから1人をランダムで選出してくれる感じをイメージしてます。

やること

  1. Slack appの作成
  2. Bolt on Dockerの実装
  3. ngrokでローカル環境を公開
  4. slash commandの作成
  5. ローカル環境で動作確認
  6. GCRへDocker imageを登録
  7. Cloud Runにサービスを登録
  8. slash commandのリクエストURLをCloud Runに向き先変更
  9. 本番環境で動作確認

並べてみると手順多いですね。一個一個はそんななのではりきっていきましょう!

Slack appの作成

まずはSlack appを作成します。GUIで。
Slack appはSlack apiのYour Appsのページから「Create New App」をクリックすれば作成することができます。
image.png

App NameDevelopment Slack Workspaceの入力・選択が求められるので教えてあげます。App Nameは今回は『test』にしてますが、本番利用時は『Pokemon Master』にする予定です。

これでひとまずSlack appの作成は完了です。以下のようにSlack appの詳細ページに遷移していると思います。
image.png

次にSlack appに情報へのアクセス権限を与えてあげます。
左のサイドメニューから『OAuth & Permissions』を選択します。ここでSlack botのSlackのワークスペースやチャンネルの情報へのアクセス権限を付与することができます。以下の権限を追加してあげます。
image.png
付与した権限は以下の通り。

  • commands -> slash commandを使える権限
  • chat:write -> slackにコメントを投稿できる権限
  • channels:read -> パブリックチャンネルの情報の閲覧権限。あとでslash commandが入力されたパブリックチャンネルのユーザー一覧を取得するために必要。
  • groups:read -> プライベートチャンネルの情報の閲覧権限。あとでslash commandが入力されたプライベートチャンネルのユーザー一覧を取得するために必要。
  • im:read -> ダイレクトメッセージの情報の閲覧権限。あとでslash commandが入力されたダイレクトメッセージのユーザー一覧を取得するために必要。
  • mpim:read -> グループダイレクトメッセージの情報の閲覧権限。あとでslash commandが入力されたグループダイレクトメッセージのユーザー一覧を取得するために必要。

これが完了したら、同じページの上の方に「Install App to Workspace」ボタンがあるのでクリックします。(クリック後に認可画面が出ますが「Allow」です。

これでSlack appの作成とWorkspaceへの登録が完了しました。

Bolt on Dockerの実装

次にBoltでSlack appの本体(slash commandを受け取った後の処理)を作っていきます。
まず、Boltの環境を整えたいのですが、Boltはnode.jsで動作するのでそれ用のDockerfileを用意します。

Dockerfile
FROM node:13.7.0-stretchENV HOME="/app"WORKDIR $HOMECOPY . $HOMEEXPOSE 3000CMD ["node", "app.js"]

Dockerfileの詳細は省きますが、最終的にapp.jsを起動させるようにしてます。

また環境変数とかをいちいちオプション指定するのも面倒なのでDocker Composeも使っていきます。

docker-compose.yml
version:'3'services:app:build:.environment:-SLACK_SIGNING_SECRET=XXXXXXXXXX-SLACK_BOT_TOKEN=XXXXXXXXXXvolumes:-.:/appports:-3000:3000

ここでSLACK_SIGNING_SECRETSLACK_BOT_TOKENの二つの環境変数を指定しています。
これらは先ほどSlack apiページで作成したSlack appの値で以下のところで確認することができます。

  • SLACK_SIGNING_SECRET: 「Basic Information」の「App Credentials」の「Signing Secret」
  • SLACK_BOT_TOKEN: 「OAuth & Permissions」の「OAuth Tokens & Redirect URLs」の「Token for Your Workspace」の「Bot User OAuth Access Token」

上のdocker-compose.ymlXXXXXXXXXXの部分はそれぞれの値に置き換えてください。

また、docker imageの中にDockerfiledocker-compose.ymlは不要なので含まないように.dockerignoreを記述しておきます。

.dockerignore
.dockerignore
Dockerfile
docker-compose.yml

ここで一度docker imageをbuildしておきます。

$ docker-compose build

次にnpmでBoltをインストールします。

$ docker-compose run app npm init
$ docker-compose run app npm install @slack/bolt

npm init時にアプリ情報を聞かれますので、いい感じに答えてあげてください。

これでBoltのインストールが完了したので、実際にアプリを書いていきます。

app.js
const{App}=require('@slack/bolt');constapp=newApp({token:process.env.SLACK_BOT_TOKEN,signingSecret:process.env.SLACK_SIGNING_SECRET});app.command('/kiminikimeta',async({command,ack,say,context})=>{ack();try{// slash commandが叩かれたChannelに所属する全ユーザー情報を取得するconstresult=awaitapp.client.conversations.members({token:context.botToken,channel:command.channel_id});constmembers=result.members;// 取得した全ユーザーから今回作成しているSlack appのBotを除外するconstbot_idx=members.indexOf(context.botUserId);if(bot_idx>-1){members.splice(bot_idx,1);}// Bot以外のユーザーから1つをランダムで選び、Channelに投稿するconstmember=members[Math.floor(Math.random()*members.length)];say(`<@${member}、キミに決めた!`);}catch(error){console.log(error);}});(async()=>{awaitapp.start(process.env.PORT||3000);console.log('⚡️ Bolt app is running!');})();

app.command()以外の部分はBoltの型みたいなものなので無心で。app.command()のところは少し補足をしていきます。

まず、/kiminikimetaのslash commandを受け取れるようにapp.commandの引数に指定してます。

最初にack()を呼び出してます。これはお作法みたいです。Boltアプリがcommandを受け取ったことをSlack appに教えてあげています。これをしないとタイムアウトとかになってしまうのでお忘れなく。

この後はtry~catchでslack web apiを使っています。
conversations.members APIでcommandが送信されたチャンネルのユーザーを取得します。

app.js(抜粋)
// slash commandが叩かれたChannelに所属する全ユーザー情報を取得するconstresult=awaitapp.client.conversations.members({token:context.botToken,channel:command.channel_id});constmembers=result.members;

conversations.membersを利用するにはtokenchannelのパラメータが必須項目です。
tokenBot User OAuth Access Tokenのことですが、これはcontext.botTokenで取得することができます。
channelはslash commandが入力されたチャンネルを設定したいのですが、これはcommand.channel_idで取得することができます。
最後にレスポンスの内容からメンバーのIDの配列をmembersに格納しています。

app.js(抜粋)
// 取得した全ユーザーから今回作成しているSlack appのBotを除外するconstbot_idx=members.indexOf(context.botUserId);if(bot_idx>-1){members.splice(bot_idx,1);}

次に先ほど取得したslash commandが入力されたチャンネルのユーザー一覧から今回のBot用のユーザーを除外します。BotのIDはcontext.botUserIdで取得ができまして、上のコードで先ほどのmembersからBotのIDと一致する文字列を除去しています。

app.js(抜粋)
// Bot以外のユーザーから1つをランダムで選び、Channelに投稿するconstmember=members[Math.floor(Math.random()*members.length)];say(`<@${member}>、キミに決めた!`);

Botを除外したなかからランダムで1つのUser IDを取得してmember変数に代入しています。
最後にsayメソッドを使ってslash commandの結果をチャンネルにポストします。
ここで<@${member}>とすることで、選ばれたユーザーにメンションをつけることができます。

ここまでがslash commandを入力されたチャンネルでランダムに1ユーザーが選ばれる処理の内容になります。結構シンプルですね。

ngrokでローカル環境を公開

ここまでで大体コーディングは完了です。実際に期待通りに動くか、まずはローカル環境で確認してみましょう。
といったものの、Slack appからローカルのパソコンにアクセス手段は基本的にありません。
そこでSlackの公式でもおすすめされている方法として、ngrokを使ってローカルで動くアプリにインターネット経由でアクセスできるようにトンネルしてあげます。

Slackでも使い方がこちらの記事で紹介されています。
簡単にまとめると、

  1. ngrokのダウンロードページで自分のクライアントにあったファイルをダウンロードする
  2. ダウンロードしたファイルをunzipする。 Downloadsディレクトリにダウンロードされた場合は、$ unzip ~/Downloads/ngrok.zip
  3. ngrokが解凍されるので、$PATHにファイルを移動。 通常/usr/local/binとかだと思うのでその場合は、$ mv ~/Downloads/ngrok /usr/local/bin
  4. コマンドが使えるようになるためにターミナル再起動かshellの再読み込み。bashを使っている場合は多分$ source ~/.bashrc

をやります。こうするとngrokコマンドを実行できるようになります。
今、DockerコンテナはPort 3000でやりとりができるようにしているので、以下のコマンドを実行してインターネットからDockerコンテナにアクセスできるようにします。

$ ngrok http 3000

Session Status                online
Session Expires               7 hours, 59 minutes
Version                       2.3.35
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://xxxxxxxx.ngrok.io -> http://localhost:3000
Forwarding                    https://xxxxxxxx.ngrok.io -> http://localhost:3000

なんだか色々出てきますが、これだけでhttp://xxxxxxxx.ngrok.ioまたはhttps://xxxxxxxx.ngrok.ioでアクセスするとhttp://localhost:3000またはhttps://localhost:3000にフォワーディングされるようになっています。

おっと、Dockerコンテナをまだ立ち上げていませんでしたね。

$ docker-compose up

はい。これでローカル側のテスト準備は完了しました。

slash commandの作成

次にSlack appの方でslash commandを作成していきます。slash commandを作成するときにコマンドを投げる先のURL(Bolt appのURL)が必要になるので先にngrokの設定をしていたのです。

slash commandはSlack appの管理画面の「Slash commands」メニューから「Create New Command」を選択することで作成できます。

image.png

「Create New Command」のモーダルが表示されますので適宜情報を入力しますが、

  • Command: /kiminikimeta (Bolt側で指定したslash commandと同じであればOK)
  • Request URL: https://xxxxxxxx.ngrok.io/slack/events
  • Short Description: 適当に何か
  • Usage Hint: 入れたければ適当に何か

といった感じに入力してください。注意が必要な点としてはRequest URLは先ほどngrokで作られたURLの後ろに/slack/eventsのパスを追加してください。Boltがこのパスでリクエストを受け取るようになっているからです。

入力がおわったら「Save」してslash commandの作成完了です。

ローカル環境で動作確認

ここまで行けばテスト準備万端です。
Slack appをインストールしたSlack Workspaceに行ってみましょう。
そして何か適当なテスト用チャンネルなどに今作ったSlack appを「Add App」して呟いてみましょう。

/kiminikimeta
Group 5.png
うわぁ!俺かぁ!

ということでこれでSlackからSlack appがslash commandを受け取り、ローカルで起動させているBolt on Dockerの処理結果をSlackにポストできていることが確認できました!

あとはこのコンテナをどこかにあげればいいだけですね。
コンテナとngrokCtrl+Cで一度落としておきましょうか。

GCRへDocker imageを登録

今回は本番環境のアーキテクチャとしてGCPのCloud Runを選択しました。
コンテナをサーバーレスで動かしてくれるマネージドサービスです。ちょちょちょっと設定すれば、アクセスがあったときだけコンテナをデプロイして動いてくれるやつです。
選んだ理由はイケている気がしたからです!(こういうの大事だと思っている)

まず、GCPのコンテナレジストリであるGCR(Google Container Registry)に今回作ったコンテナイメージをアップロードしていきたいと思います。

GCP及びgcloudコマンドはすでに使える状態という前提でお話します。 => gcloudコマンドがまだのかたはこちら

GCRではコンテナイメージは[HOSTNAME]/[PROJECT-ID]/[IMAGE]の形式でpushすることができます。
HOSTNAMEはリージョンによって異なりますが、アジアの場合は「asia.gcr.io」を使うことができます。
PROJECT-IDは各々のGCP PROJECTの名前で、IMAGEは好きにつけてOKです。今回はIMAGEkiminikimetaにしましょう。PROJECT-IDは仮にで「sample」とします。
つまりDocker imageのイメージ名はasia.gcr.io/sample/kiminikimetaになるということです。

Docker composeでビルドした際にこの名前のイメージができるように、docker-compose.ymlを更新します。

docker-compose.yml
version:'3'services:app:build:.image:asia.gcr.io/sample/kiminikimeta#追加environment:-SLACK_SIGNING_SECRET=xxxxxxxxxx-SLACK_BOT_TOKEN=xxxxxxxxxxvolumes:-.:/appports:-3000:3000

更新が完了したらbuildします。

$ docker-compose build

Successfully tagged asia.gcr.io/sample/kiminikimeta:latest

ちゃんと指定したイメージ名でDocker imageが作成されましたね。
後はこれをGCRにpushします。

$ docker push asia.gcr.io/sample/kiminikimeta:latest

Cloud Runにサービスを登録

Docker imageのpushがおわったらCloud Runの設定をしていきます。これはGUIでやってみます。
メニューから「Cloud Run」を選択しコンソールに移動したら「サービスを追加」を選択します。
image.png

ここで各種設定をしていくのですが、以下のように設定しました。

  • コンテナイメージのURL: 「選択」から先ほどpushしたイメージを選択する
  • 「Cloud Run」 or 「Cloud Run for Anthos」: 「Cloud Run」
  • Cloud Runのリージョン: Tokyo
  • サービス名: kiminikimeta
  • 認証: 未認証の呼び出しを許可
  • コンテナポート: 3000
  • 環境変数:
    • SLACK_SIGNING_SECRET: xxxxxxxxxx
    • SLACK_BOT_TOKEN: xxxxxxxxxx

「コンテナポート」と「環境変数」は「オプションのリビジョン設定を表示」をクリックしないと出てこないので見つけにくいかもしれないですね。その他の値はデフォルトのままでOKです。
ここまでできたら「作成」を選択してCloud Runサービスを作成します。

サービスの作成が完了すると以下のような画面になっていると思います。
Group 6.png
右上の「URL」という部分がこのCloud Runサービスが動くトリガーとなるエンドポイントです。

slash commandのリクエストURLをCloud Runに向き先変更

Slack appのslash commandはまだngrokのURLにリクエストを飛ばすように設定されているので、最後にエンドポイントをCloud RunのURLに更新します。

image.png
Slack appのslash commandsメニューの画面をみると、こんな感じに今登録されているslash commandを確認することができます。この鉛筆マークを押すと、そのslash commandの詳細を変更できるので、ここからURLを先ほどのCloud Runで作成したサービスのURLに更新しましょう。
このとき、/slack/eventsをパスに追加することをお忘れなきよう。
(Cloud RunのサービスのURLがhttps://xxxxxxxxxxx.run.appの場合、https://xxxxxxxxxx.run.app/slack/eventsと更新する。)

本番環境で動作確認

さて、ここまでで全ての作業がおわりました。最後にもう一度、Slackで/kiminikimetaを試してみましょう。

/kiminikimeta
/kiminikimeta
Group 7.png

ちゃんとランダムにユーザーが選ばれていますね!!

おわりに

今回はBolt on Docker + Cloud RunでSlash commandを作ってみました。
Dockerは元々使っていたのであれですが、Bolt & Cloud Runは初めましてだったにもかかわらず意外とスムーズにできた印象です。
Boltはドキュメントがそれなりにまとまっているし、console.logでデバッグしながらやればいろいろなことができそうですね。
Cloud Runはかなり直感的に使うことができるとわかりました。お値段次第ですが、これはけっこうおすすめかもしれない。

今回のOutput

今回のBolt on DockerのアプリはGithubにあげてます。気になる方は参考にしてください!

Reference

Node cron 実行スケジュールをランダムにしたいです

$
0
0

Nodecron、puppeteerrecorderを使って、自動化プログラムを作ったのですが
実行スケジュールをランダムに実行したいです。
毎分の実行や、毎分何%の確率で実行などは調べて出てきたのですが、
分、秒数をランダムで実行したいです。

例えば
プログラムを実行したら毎分30分ごとに
29分48秒で実行
17分26秒で実行
10分54秒で実行
のようにしたいと思っております。

const cron = require('node-cron');
cron.schedule('*/30 * * * * ', () =>

この後にプログラムを書いているのですが、これですと毎分きれいに30分ごとの実行になってしまいます。
プログラミングを始めたばかりで質問も初めてなのですが、よろしくお願いいたします。

ruby のあれは、 node.js でどう書くんだ

$
0
0

js むずい

rubynode.js
File.read('file.txt')const fs = require('fs')
fs.readFileSync('file.txt', 'UTF-8')
File.write('file.txt', content)fs.writeFileSync('file.txt', content)

以下、つど調べたら追記

React-Nativeのinitを実行しようとしたら、Error: Cannot find moduleとエラー表示がされる。(windows10)

$
0
0

実行環境

・OS: Windows10
・react-native-cli: 2.0.1
・react-native: 0.61.5
・Python 3.7.0
・javac 11.0.2
・Android Debug Bridge version 1.0.41
 Version 29.0.5-5949299

公式ページ

https://facebook.github.io/react-native/docs/getting-started.html

になぞらえてReact-Nativeを始めようと思ったら、最初のinitでいきなり躓きました。

起きたエラーというのが、

npx react-native init AwesomeProject

というコマンドを実施すると↓のように表示されるというもの。
react-native-error.png

Error: Cannot find module 'C:\Users\user\AppData\Local\Temp\rncli-init-template-******\node_modules\react-native\template.config

解決策としてyarnで実行してみる、
react-nativeとcliのバージョンを変えてみる、
npm uninstall -g react-native-cliを実行した後、再度インストールし直すなど、
色々と試しましたが、このエラーは消えませんでした。

しまいにはwindow10では現時点ではエラーが発生するため、
次のバージョンアップを待つしかない、といった記事も見つけたりして途方に暮れていました。

しかしそんなとき、以下の方法で試してみたら、
無事上手くいきました。

コマンドプロンプトを「管理者として実行」する。
npm uninstall -g react-native-cli
npm install -g react-native-cli
react-native init MyApp

これで無事成功しました!

react-native.png

解決策がまったく見つからなかったので記事にしてみました。
お困りの方は是非試してみてください。

(実は公式の一番上に、管理者で実行するよう書いてあるのですが、
pythonとjdkが導入済みだったためスルーして気づきませんでした。)


Slackアプリ(OAuth)を数時間で開発し、Slack App ディレクトリに35時間で掲載された話

$
0
0

Repsona LLCの代表兼エンジニア(ひとり)の、ガッシーです。ひとりで、「理想のプロジェクト管理ツール」Repsona(レプソナ)を作っています。





みんな大好きSlack連携を実装しました。案外簡単で、Slack App ディレクトリへの掲載ハードルも高くなかったのです。実装や掲載までの方法、ハマったポイントなどを書きたいと思います。同様の機能を検討している方の参考になれば嬉しいです。

なぜ作ったのか?

Slack連携は、Repsonaユーザーのみなさまからかなり多くのご要望をいただきました。開発後すぐにSlackコミュニティでも連携開始しましたが、更新がリアルタイムで確認できるのはかなり便利です。

Incoming WebhooksでWebhook URLを払い出してユーザーにコピペしてもらう方式でも同様の連携は実現できるのですが、Repsonaの設定画面からクリックだけで連携が完了する体感を作りたかったのです。





これを実現するためには、「Slackアプリであること」と「App ディレクトリへの掲載」が必要でした。

Slackアプリ(OAuth)の仕組み

簡単な流れは下記のような感じです。

  • Your Appsの画面でSlack上に「アプリ」を作る
  • ユーザーに、ワークスペースへのインストールを許可してもらう
  • 許可してもらうとアクセストークンがもらえる(OAuth)
  • アクセストークンを使うとAPIをコールできるようになる

シーケンス図にするとこんな感じです(これが公式ドキュメントになくて、なかなか流れがイメージできなかった・・ので、初めての方にはかなり参考になるのではないでしょうか)。

image.png

登場人物は「ユーザー」、「サーバー(ここではRepsona)」、「Slack API」、「Slackアプリ」です。この流れがわかってしまえばもう簡単です。

Slackアプリの作り方

Slack上で「アプリ」を作ります

Create New AppからApp Nameを入力してCreate App。これだけです。
スクリーンショット 2020-01-29 10.28.08.png

アプリ名やアイコンなどを設定をしていきます。わくわくしますね。

スクリーンショット 2020-01-29 10.36.47.png

スコープに、Incoming Webhooksを設定して、なぜ必要なのか(Request Reason)を記載します(App ディレクトリへの申請で必要です)。

スクリーンショット 2020-01-29 10.38.41.png

リダイレクトURLを指定します。ここに設定したURLにマッチしたものしかリダイレクトできません。

スクリーンショット 2020-01-29 10.40.12.png

Save URLs を押さないと反映されないので注意です。

Slackの認証画面を表示してみる

OAuthを開始するためにパラメータをつけてSlackにリンクします。

<ahref="https://slack.com/oauth/v2/authorize?scope=incoming-webhook&client_id=00000000.000000000000&redirect_uri={redirect_uri}">Slack連携する</a>

scopeにはincoming-webhookclient_idは、Basic Informationに記載されてます。redirect_uriにはリダイレクトで戻したいURLをいれます。

スクリーンショット 2020-01-29 10.55.39.png

クリックしてアクセスするとインストール許可の画面が出てきます。ここまでくると興奮しますね。

スクリーンショット 2020-01-29 12.04.06.png

でもまだサーバーが実装されていないので動きません。

サーバーを実装します

RepsonaサーバーはNodeで開発していますが、API利用はSDKが用意されておりかなり便利です。以下、SDKを使ったサンプルです。

ユーザーが「許可する」をクリックすると、リダイレクトURLにリダイレクトされてきます。このときワークスペースへのインストールが成功しているとcodeという引数がついてきます。

これを使って、以下のようにアクセストークンを受け取り、保持する実装をします(公式ドキュメント)。

const{WebClient}=require('@slack/web-api');// App credentials found in the Basic Information section of the app configurationconstclientId=process.env.SLACK_CLIENT_ID;constclientSecret=process.env.SLACK_CLIENT_SECRET;// Not shown: received an authorization grant called `code`.(async()=>{// Create a client instance just to make this single call, and use it for the exchangeconstresult=await(newWebClient()).oauth.v2.access({client_id:clientId,client_secret:clientSecret,redirect_uri:redirectUri,code:code});// It's now a good idea to save the access token to your database// Some fictitious databaseawaitdb.createAppInstallation(result.team_id,result.enterprise_id,result.access_token,result.bot);})();

codeに渡ってきたcodeを、redirect_uriにはSlackの認証画面に飛ばす時に渡したredirect_uriを設定します。これが一致しないと通りません。

成功するとこんな感じアクセストークンチャンネルID、そしてWebhook URLが取得できます。

{"ok":true,"access_token":"xoxp-XXXXXXXX-XXXXXXXX-XXXXX","scope":"identify,bot,commands,incoming-webhook,chat:write:bot","user_id":"XXXXXXXX","team_name":"Your Workspace Name","team_id":"XXXXXXXX","incoming_webhook":{"channel":"#channel-it-will-post-to","channel_id":"C05002EAE","configuration_url":"https://workspacename.slack.com/services/BXXXXX","url":"https://hooks.slack.com/TXXXXX/BXXXXX/XXXXXXXXXX"}}

これをDBなどに永続化して、この後の連携に利用します。

Webhook URLが取得できればあとは簡単です(公式ドキュメント)。jsonをpostするだけでご指定のチャンネルにメッセージが送れます。Repsonaでは更新があるごとに、多少整形して、Webhook URLにpostする実装を追加しました。

POST https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
Content-type: application/json
{
    "text": "Hello, world."
}

SDKでやるならこんな感じです(公式ドキュメント)。とっても簡単ですね。

const{IncomingWebhook}=require('@slack/webhook');consturl=process.env.SLACK_WEBHOOK_URL;constwebhook=newIncomingWebhook(url);// Send the notification(async()=>{awaitwebhook.send({text:'I\'ve got news for you...',});})();

Slack App ディレクトリとは?

Slackチームが審査したアプリが掲載されている場所です。ここに掲載されているアプリは、どのワークスペースでもインストールすることができるようになります。

Repsonaは申請からたった35時間で掲載されました。Slackチームさん仕事早い!!
https://slack.com/apps/ASNKGK9U3-repsona

Slack App ディレクトリに掲載するには?

公式のSlack App ディレクトリにアプリを提出しように全てが書いてあります。

スクリーンショット 2020-01-29 12.21.50.png

英語で47個チェック項目がありますが、苦手な方でもGoogle翻訳などを使えば簡単です。申請する前に確認しておいた方がいいものは下記あたりです。

  • トークン等をハードコーディングしていないこと
  • スコープの使用目的を記載しているか
  • リダイレクトURLはhttpsであるか(テスト用も残しちゃダメ)
  • インストールまでの詳細な説明を書いたランディングページ
  • プライバシーポリシーのページ
  • サポートの手段(メールアドレスなど)
  • 審査のためのログインアカウントなどが必要であれば用意する

苦労話

  1. そもそもの流れを掴むのに苦労しました(初歩的ですが、どうやって始めるのか、codeがどこからくるのかがわからなかったり・・)。

  2. 公式ドキュメントは、実装の流れよりも、説明の流れが重視されているので、やりたいことをやるために、かなりいったりきたりしました。この辺とかこの辺とかこの辺とか

  3. やりたい連携の仕組みの作り方から、App ディレクトリ申請までを含めた一連の情報がまとまっていなくて、開発できるまで情報を整理するのに苦労しました。

まとめ

  • Slackアプリ作ること自体はとっても簡単
  • Slack App ディレクトリの敷居は決して高くない
  • Slackの中の人はテンションが高くて好き

苦労話はありましたが、実装は簡単で、Slackチームはスピーディーで、やりたいことは簡単に実現できました。いまは更新情報をWebhookしているだけですが、今後はRepsona内のタスクや情報の追加更新や検索などもできるようにしていきたいと思います。

NODE_ENV ってなんだ。

$
0
0

Node.js で、慣習的によく使う環境変数の名前らしい。

$ #  1. 環境変数を指定すれば...
$ export NODE_ENV='development'
$ node
> // 2. process.env から参照できる。
> process.env.NODE_ENV
'development'
>

development以外にも production, testが指定されたりする模様。

環境変数を使う理由

開発環境のパソコンでは developmentを指定して、商用環境のサーバでは productionを指定しておけばデプロイするたびにコードを切り替える必要がなくなる。

手作業でこの切り替えを行うと忘れが発生して開発時のセキュリティ的にヤバいコードをデプロイしてしまう可能性があるので、このようにして環境変数を利用するとどこかで聞いた。

process

processはどこからでも参照できるオブジェクトで globalの仲間らしい。

The process object is a global that provides information about, and control over, the current Node.js process. As a global, it is always available to Node.js applications without using require(). It can also be explicitly accessed using require():

constprocess=require('process');

process.env

process.envは環境変数を持っている。

The process.env property returns an object containing the user environment. See environ(7).

global

These objects are available in all modules.

Gulpとは?使う環境を整えよう

$
0
0

はじめに

未経験のWeb中級者です。
効率的なWeb制作を実現するために、Gulpを使ったフロントエンドの開発環境の構築方法について学びました。

開発環境:Macでターミナルを使いました。

Gulpとは

さまざまな処理を自動で行なってくれるタスク自動化ツールで、一度設定しておくと開発の作業効率が図れます。

Gulpはタスクランナーと呼ばれていて、決められたタスクをJavaScripを用いてプログラムすることができます。

メリット

導入が簡単でプラグインが豊富
タスクランナーはGulpが人気!!

Gulpでどんな事ができるか

1.JavaScriptのES6+からES5へのトランスパイルが自動化(ESLint連携)
2.SassファイルをCSSファイルに自動でプリコンパイル(node-sass連携)
3.画像のリサイズや圧縮を自動化(GraphicsMagick連携)
4.JavaScriptファイル、CSSファイルのminifyを自動で行える

他、さまざまなプラグインがあるので、やりたいことに合わせてインストールができます。

実行する環境を整える

1.node.jsをインストール

Gulpが動作する環境はnode.jsがありインストールするとターミナルからJavaScriptを実行できるようになります。使うにはnode.jsにさまざまなモジュール(部品)をインストールして繋ぎ合わせて実行します。https://nodejs.org/ja/
screencapture-nodejs-org-ja-2020-01-30-00_28_53.png
LTS(長期間サポートが保証されているバージョン)を選び

Tarmnar
npm -v

↑ターミナルで入力してインストール。

Tarmnar
npm -v
v6.9.0

↑こちらが出たらOK。

私は、エラーが出てしまい...こちらを参考にして解決しました!
npmでpermission deniedになった時の対処法
https://qiita.com/okohs/items/ced3c3de30af1035242d

2.yarnをインストール

yarnはパッケージ管理ツールでnode.jsにインストールするモジュールの管理を行います。
screencapture-legacy-yarnpkg-ja-docs-install-2020-01-30-00_47_20.png

Tarmnar
npm install -g yarn

↑ターミナルで入力してインストール。

Tarmnar
yarn -h

↑インストールできているか確認。
インストールした内容が出ればOK。

3.Gulpをインストール

Gulpをインストールするにはターミナルを使います。
node.jsをインストールした時にnpmというパッケージ管理ツールが一緒にインストールされていますが、yarnの方が高速なのでこちらを使っていきます。

screencapture-gulpjs-2020-01-30-01_24_31.png

1.プロジェクトフォルダーを用意

私の場合はtestフォルダの中に以下のファイルを準備しました。
スクリーンショット 2020-01-30 1.46.50.png

2.globalをインスストール

Tarmnar
yarn global add gulp-cli

globalをターミナルに付けることによって、このプロジェクト以外からでも使えるようになりnode.jsで使うモジュールをインストールする事ができます。

※ターミナルでフォルダ内に移動していないとエラーが出てしまいます。
cdと打ってからファイルごとドラック&ドロップして移動すると簡単です。

3.プロジェクト内の設定・初期化・確認

Tarmnar
yarn init -y

↑ターミナルで打つと、package.jsonというnode.jsの設定ファイルがtestフォルダ内に作成されます。こちらにモジュールが追加されて情報が管理されます。

スクリーンショット 2020-01-30 2.11.11.png

Tarmnar
yarn add gulp -dev

↑ターミナルで打つとgulpのインストールが完了!
package.jsonの中身に"devDependencies": {"gulp": "^4.0.2"と追加されていればOK。

また、testフォルダ内にyarn.lock(ファイル)とnode_modules(フォルダ)もインストールされているかを確認してください。

yarn.lock→インストールされた状況を記載
node_modules→沢山のモジュール達が格納されている

Tarmnar
 gulp --help

↑インストールされていいるかを確認できます!

最後に

無事に環境を整えられました!
次回は、基本的な文法を学ぼうと思います。

Gulpについては、こちらの記事も参考になります。
https://flex-box.net/gulp/

react-qr-readerを(Windowsで)使うときにはまったこと(2020年1月時点)

$
0
0

Node.jsの環境を作っておく

QRコードリーダーの作り方の記事にはnpxとかいきなり出てくるので、Node.jsの環境はあらかじめ構築しておく必要はあります。
 最近はyarnを使うと速いのでそれを使ったりする。yarn自体にもインストーラーがありますが、Chocolateyからyarnをインストールするというのもあります。

Chocolateyを使った環境構築の時のメモ

Yarn を導入しよう(Windows)

QRコードリーダーを作る

作り方は↓を参照。基本的にはこれで作れます。PCからはlocalhost:3000でアクセスできます(Webカメラがついていれば)が、iPhoneと同様Androidのスマホでもhttpsでなければカメラにはアクセスできません(権限の確認もない)。

react-qr-reader を利用したQRコードリーダーの作成

WindowsのReactをHTTPSで使うには

SET HTTPS=true&&yarn start

などとします。実行するディレクトリ/フォルダは上の記事で作ったqrcode-reader直下です。Windowsのファイヤーウォールの設定もいじったかも。

はまったこと

HTTPSで動かすと、見事に下の不具合にぶち当たりました。

create-react-appで作ったアプリがhttpsだと動かない

 現時点ではまだreact-scriptの3.3.1は出ていないので、記事に書いてある更新後のpackage.jsonに書き換えて、

npm install

で無事動作するようになりました。あとは、自分の目的に応じてApp.jsを書き換えましょう。

Node.jsをインストールする

$
0
0

はじめまして, Sefoo(せふ)です。
Node.jsをインストールして確認するまでをまとめた。参考にして頂ければ幸い。

背景

デスクトップアプリを作りたくてElectronに興味を持った。ただ, JavaScriptやNode.jsは少し触ったことがある程度。これを機にちゃんと勉強しようと思う。
とりあえずNode.jsで簡単なものを作ってみることにする。さて, まずはNode.jsのインストールから始めよう。ちなみに自分はWindows 10 OS環境で開発する。

Node.jsをダウンロードする。

Node.jsは後で記述するが, JavaScriptの実行環境のことだ。
Node.jsのHPからダウンロードする。画面左の推奨版を押す。
インストール完了の際, バージョン番号で確認するので覚えておくとよい。自分の場合は「12.14.1」。
縮小 - Nodejs install.png

Node.jsをインストールする。

ダウンロードされたインストーラを起動する。基本的にインストーラに従って次々ボタンを押すだけでよい。
Nodejs install 1.png
Nodejs install 2.png
「I accept the terms in the License Agreement」にチェックを入れるのを忘れないように。「ライセンスに同意する」の意。
Nodejs install 3.png
インストール先を変えたい方はここで変更する。自分はこのまま。
Nodejs install 4.png
デフォルトのままで次へ。インストールするのはNode.js本体とNode Package Manager(npm)。あと, パスも自動で通してくれるらしい。ドキュメントへのショートカットもインストールすると読めるが見つからなかった。
Nodejs install 5.png
チェックを外して次へ。自分の環境は決してディスク容量が大きいわけではないので, 極力余計なモノは入れたくない。必要になれば自分で入れる。「いくつかのライブラリに必要なモジュールをインストールしますか?」の意。
Nodejs install 6.png
Nodejs install 7.png
Nodejs install 8.png
おわり。

ちゃんと動くのか確認する

コマンドプロンプトを起動して確認する。
コマンドプロンプトの場所.png

以下のコマンドをタイプする。

node.jsのバージョンを確認
node --version

ダウンロードしたNode.jsのバージョンと同じ数字が返ってくれば問題なくインストールされている。

node.jsのバージョン結果
v12.14.1

Node.jsと一緒にnpmもインストールされているのでそちらも確認する。

npmのバージョンを確認
npm --version
npmのバージョン結果
6.13.4

そもそもNode.jsとは何か

Node.jsはJavaScriptの実行環境のこと。詳細はNode.jsのHPから確認してほしい。
JavaScriptは元々ブラウザ上で動くプログラム言語だが, Node.jsは自身のPCやサーバサイドでJavaScriptを動かすことができる。

Node.jsは大規模で高速なネットワークを構築するコンセプトの元で開発された。

Node's goal is to make non-blocking network programming accessible to users who are not well-versed in server development
Ryan Dahl

Node.jsはC10K問題を解決する。Apache HTTP Serverはどれだけ高性能なサーバを用意しても1万接続を超えるとパフォーマンスが上がらない。プロセスやスレッドが接続数に応じて増えることが原因と言われている。Node.jsはイベントループとノンブロッキングI/Oによりこの問題を解決した。

C10K問題(英語: C10K problem)とは、Apache HTTP ServerなどのWebサーバソフトウェアとクライアントの通信において、クライアントが約1万台に達すると、Webサーバーのハードウェア性能に余裕があるにも関わらず、レスポンス性能が大きく下がる問題である。
C10K問題 - Wikipedia 参照

Node.jsなら小規模のデータや計算ならば高速に処理できて, メモリの消費量も抑えらえる。

最後に

最後まで読んで頂き, 感謝。
Node.jsとnpmのインストールについてまとめた。特に難しい点もなく, 時間もかからなかった。
Node.jsのバージョンを上げる時は同じ手順でNode.jsのHPからインストールすればよい。また, コマンドラインからバージョンアップできるツールもあるようだ。MacOSならnが有名か。導入したら記事を上げるかもしれないので乞うご期待。

参考資料

Viewing all 8839 articles
Browse latest View live