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

GitHub APIのレスポンスが 403 Forbidden になったら

$
0
0

User-Agentヘッダーは設定していますか??
https://docs.github.com/en/rest/overview/resources-in-the-rest-api#user-agent-required

コード例

以下はissueを作成するコードです。
認証はパーソナルアクセストークンで行います。

PersonalAccessToken, organization, repositoryはご自身のものに置き換えてください。

github.ts
importhttpsfrom'https'constcreateIssue=async(title:string,body:string,labels:string[]):Promise<number>=>{returnnewPromise((resolve,reject)=>{constreq=https.request({method:'POST',headers:{'User-Agent':'YourApp',// これ大事'Accept':'application/vnd.github.v3+json','Content-Type':'application/json','Authorization':`token ${PersonalAccessToken}`},host:'api.github.com',path:`/repos/${organization}/${repository}/issues`,},(response)=>{letres:any=''response.on('data',(chunk)=>{res+=chunk})response.on('end',()=>{res=JSON.parse(res)resolve(res.id)})response.on('error',(err:Error)=>reject(err))})req.write(buildPayload(title,body,labels))req.on('error',(err:Error)=>reject(err))req.end()})}constbuildPayload=(title:string,body:string,labels:string[]):string=>{returnJSON.stringify({title,body,labels})}

まとめ

全ての原因がこれに当てはまるかは分かりませんが、私はこれで解決できました。
@octokit/rest.jsなどを使った方が早いかもしれませんね)


Node.jsでtmpに一時ディレクトリを作るにはfs.mkdtempSyncを使う

$
0
0

Node.jsで/tmpに一時的なディレクトリを作るには、fs.mkdtempSyncを使います。

constfs=require('fs')constdir=fs.mkdtempSync('/tmp/foo')console.log(dir)//=> "/tmp/foo4xeXpJ"

はじめてのAngular入門ガイド

$
0
0

Angularプロジェクトの概要と始め方を記載します。Versionの変更がたびたびあるので、内容はリンクは最新のVersionのページを確認すること。

社内教育に使っている資料なのでメモ書きです。

URL

Angular ドキュメント

Angular 基本概念

Angular チュートリアル

アプリケーション生成

ngnewapplication-nameでプロジェクト構成-Routingの設定を聞かれるので>y-cssの設定はscss

起動コマンド

ngserve

http://localhost:4200で画面

ディレクトリ構成

全体感をつかむためにプロジェクトの構造を把握します。ディレクトリ構造を知っておくとバグ時にどのファイルが原因か可能性を絞れるようになります。

コンパイル関係

tsconfig

tsconfig.json:editorから参照されるファイル?(詳細は理解しておらず)tsconfig.base.json:app.json,spec.jsonの両方に継承されているコンパイル設定tsconfig.app.json:通常のコンパイル設定tsconfig.spec.json:テスト用のコンパイル設定

angular.json

Angularプロジェクトのビルドの設定などが記述されている。よく利用して重要なのは以下の3プロパティー。

  • assets: 画像などを格納するdirectory
  • styles: cssやscssなどを格納する。node_modulesに入っているscssなどを入れる。
  • scripts: Javascriptファイルを読み込む
"assets":["src/favicon.ico","src/assets"],"styles":["src/styles.scss"],"scripts":["node_modules/libraryA/js/index.js"]

おそらく最適化などの処理をしてもらえるので、Index.htmlをいじるのでなく上のファイルをいじることでJavaScriptやCSSなどを読み込むのがよい。

その他

とりあえずデフォルトのままで問題ないファイル。

src/test.ssrc/main.tssrc/polyfills.tssrc/index.html

エディター設定ファイル

.editorconfig:エディター一般の構文設定tslint.json:typescriptの構文設定

環境設定ファイル

angular.jsonファイルのコンパイル設定で設定だれている。

envrionments.environment.ts

Angularの構文

特に重要で理解したい概念

  1. Component
  2. Provider(Service)
  3. Module

理解したい構文

以下のdeclarations, imports, providers,exports。bootstrapはとりあえずとばしてよい

@NgModule({declarations:[AppComponent,UserListComponent],imports:[BrowserModule,AppRoutingModule],providers:[],exports:[],bootstrap:[AppComponent]})exportclassAppModule{}

とりあえずすすめたいところ

Getting Started, FundamentalsのComponent Interactionまで

image.png

モジュールとコンポーネント

export, import, declarationとcomponentの関係を正確に理解するのが重要


Yet to be written.

モジュールとコンポーネントの関係を理解するためのデモソースコード
URL: https://github.com/programmerkgit/angular-module-import-demo

Angular Module

  1. Moduleのdeclareに記載されたComponentが利用できるようになる。
  2. componentは同じmodule内でdeclareされた他のcomponentを利用できる。
  3. componentは同じmodule内にimportされたModuleの中でdeclareかつexportされたcomponentを利用できる
  4. MainModuleがChildModuleをimportし、ChildModuleがGrandChildModuleをimportしている場合。ChildModuleがGrandChildModuleをexportすると、MainModuleからGrandChildModuleでexportしているModuleやcomponentにアクセスできる
MainModuledeclaration:[MainComponentA,MainComponentB],import:[ChildModule]ChildModuledeclaration:[ChildComponent,ExportedChildComponent]import:[GrandChildModule,ExportedGrandChildModule],export:[ExportedGrandChildModule]GrandChildModuledeclaration:[GrandChildComponent,ExportedChildComponent]export:[ExportedChildComponent]ExportedGrandChildModuledeclaration:[ExportedGrandChildComponent,ExportedExportedChildComponent]export:[ExportedChildComponent]

IBM Cloudのメール配信サービス(SendGrid)を使ってNode.js、Pythonからメール配信してみた!

$
0
0

背景

現在私が担当している案件で一般ユーザー向けのサービスを提供する予定で、ユーザー登録をした際などにメールをユーザーに送信する必要があるとのことで、メール送信サーバーをどうやって調達するかが課題となった。
お客様の方で用意できるメール送信サーバーで使えるものはないということだったので、IBM Cloud内で使用可能なサービスがないのかを調査した結果以下のサービスが利用できそうなので、実際に使えるのかどうか、利用方法はどんな感じかを実際にサービス登録して使ってみたのでこの記事にまとめました。
https://cloud.ibm.com/docs/email-delivery?locale=ja

E メール配信について
SendGrid の IBM Cloud E メール配信サービスは、メール・リレー・サービスです。このサービスにより、スマート・ホストを使用してアウトバウンド・メール・サービスのリレーが可能になります。スマート・ホストは、SMTP サーバー、メール・クライアント、または SMTP を処理可能な任意のサービスまたはプログラム言語からの SMTP トラフィックをリレーします。 また、このサービスでは、メトリックの生成、E メール・リストの追跡、E メール・アクティビティー (E メールのバウンス、クリック、ドロップ、オープンなど) の追跡も行います。 このサービスには、ニュースレター支援や認証などの他の機能も用意されています。

※なお、マニュアルには何が出来るかほとんど記載されていませんが、サポートに問い合わせたところ、IBM Cloud経由でSendGridに作成されたアカウントで選択したアカウント・タイプで使用可能なSendGridの機能はそのまま利用できるとのことです。

登録方法

  1. https://cloud.ibm.com/classic/services/emaildeliveryにアクセスし、Order Email Delivery Serviceのボタンをクリックする
    screencapture-cloud-ibm-classic-services-emaildelivery-2020-07-15-16_25_22.jpg

  2. Account Typeを選択し、Email AddressUsernamePasswordを設定し、Continueボタンをクリックする
    ※UsernameとPasswordはSendGridアカウントのユーザー名、パスワードとして使用される。
    screencapture-cloud-ibm-classic-services-emaildelivery-2020-07-16-10_31_17.jpg

  3. 登録が完了し一覧に表示されたら、ActionsよりAccess Vendor Portalを選択し、Send Gridのポータルにアクセスする
    2020-08-04 14_56_27-IBM Cloud __ Email Delivery Service.jpg

  4. 手順3で設定したUsernameとPasswordでログイン出来ることを確認する。
    screencapture-app-sendgrid-login-2020-07-15-16_55_32.jpg

使い方

  1. SendGridのポータルからSetup Guideを開く
    screencapture-app-sendgrid-2020-08-04-15_04_17.jpg

  2. Integrate using our Web API or SMTP Relay横のStartを選択する
    screencapture-app-sendgrid-guide-2020-08-04-15_04_34.jpg

  3. Web API以下のChooseを選択する
    screencapture-app-sendgrid-guide-integrate-2020-08-04-15_04_41.jpg

  4. 使用した言語横のChooseを選択する
    screencapture-app-sendgrid-guide-integrate-langs-2020-08-04-15_04_54.jpg

  5. My First API Key Name配下にAPIキー名を入力した上で、Create Keyボタンをクリックする
    screencapture-app-sendgrid-guide-integrate-langs-nodejs-2020-08-04-15_05_32.jpg

  6. 後は画面に表示された手順に乗っ取り該当のコードを実装し、実行すればメールが送信される
    screencapture-app-sendgrid-guide-integrate-langs-nodejs-2020-08-04-15_30_02.jpg

【Node.js】kuromoji.js + mecab-ipadic-neologdで形態素解析して遊ぶ

$
0
0

スクレイピングした文章を形態素解析したい!

nodeではjava用に書かれたkuromojiを移植したkuromoji.jsを使用するのが簡単っぽい。(自分調べ)

しかしWeb上の文字は日々変化し、思ったように解析できない場合も多い。
そこでWeb上の資源を解析して作られている新語・固有表現に強いmecab-ipadic-NEologdという辞書も導入する。
(+ベースはipadic辞書)

環境

  • Windows 10, git-bash, MINGW64(素直にlinux使ったほうが簡単です)
  • busybox(UNIXコマンド群、後で導入)
  • node v12.18.3
  • npm v6.14.7
  • kuromoji.js v0.1.2

kuromoji.js の導入

kuromoji.jsにはデフォルトで辞書が入っているため、使うだけなら非常に簡単。
サンプルのdicPathを、node_module内の辞書を参照するように変更する。

$ npm install kuromoji
sample.js
varkuromoji=require("kuromoji");kuromoji.builder({dicPath:"node_modules/kuromoji/dict"}).build(function(err,tokenizer){// tokenizer is readyvarpath=tokenizer.tokenize("すもももももももものうち");console.log(path);});

実行すると形態素解析した結果が出力されるはず。

kuromoji-js-dictionary の導入

kuromoji.jsに新しい辞書を追加するには、その辞書をkuromoji用に変換する必要がある。
これを支援してくれるスクリプトがあったので使用させてもらう。

$ git clone https://github.com/sable-virt/kuromoji-js-dictionary.git

$ cd kuromoji-js-dictionaly
$ npm install

windows環境では一部動かないので手作業で修正...

package.json
{"scripts":{+"euc":"find ./neologd-seed -name '*.csv' -exec sh -c 'nkf -e --overwrite {}' ';'",+"tar":"find ./neologd-seed -name '*.csv' -exec sh -c 'tar -cvzf ./dict/neologd/$(basename {}).tar.gz {}' ';'",-"tar":"find ./neologd-seed -name '*.csv' -exec sh -c 'tar -cvzf ./dict/neologd/$(basename '{}').tar.gz {}' \\;",}}

eucはgitのログを見る限り必要無くなったようだが、npm run convertから消し忘れている様子...。
必要ないか断定できないのでログからリバートした。
また-execコマンドはwindowsでは;で終わらせる必要があるらしいので修正。

ビルドするために他にxznkfが必要なので、無い場合は適当に用意しておく。
linux環境ならbrewなどで適当に取ってこれるがwindowsだとこれまた厄介。

xzbusyboxを使用し、nkfvictorから適当なものを用意した。
それぞれ--helpコマンドが通れば追加完了。
その他足りないコマンドは各自で。

辞書のコンバート

ここからはレドメ通りなのだが一応記録しておく。

./neologd-seedのディレクトリに使用したい辞書(****.csv.xz)を置いておく。
初めから以下のの辞書が置いてあるので、とりあえずそれを使用する。

  • ../dict/mecab-ipadic-2.7.0-20070801.tar.gz (ベースの辞書)
  • mecab-user-dict-seed.20180322.csv.xz
  • neologd-adjective-verb-dict-seed.20160324.csv.xz
  • neologd-common-noun-ortho-variant-dict-seed.20170228.csv.xz
  • neologd-noun-sahen-conn-ortho-variant-dict-seed.20160323.csv.xz
# ./dict/neologd に変換した辞書を出力$ npm run convert

# ./dist に kuromoji.js 用の辞書を出力$ ./bin/run

All done!!って言われたら完成!
./dictディレクトリを自分のプロジェクトに持ってきて、dicPathを変更すれば完成。

TypeError: Cannot read property 'lookup' of nullは辞書が見つからないエラー。
おま環か分からないが、package.jsonからの相対パスで記載すればOKだった。

新しい辞書の追加(mecab-ipadic-NEologd)

上記では予め用意された辞書を使用したが、NEologdはビルドすることで最新の辞書を作成することができる。(週2更新?)
ただそれはまた大変なので、リポジトリに定期的に保存?されているビルドされた辞書を使用する。
上記URLの ./seedからビルドされた辞書を取得できる。

試しにmecab-user-dict-seed.****.csv.xzという辞書を追加する場合...
この辞書をダウンロードして、./neologd-seedに放り込む。
前回のビルドの残骸(***.csv)が残っている場合、消しておいてもよい。
その後、上記の「辞書のコンバート」の章と同じことをやれば完成だ。

他の辞書もこれと同様の手法で導入できる(はず)。

NEologdの効能

NEologdを今試しているサンプルに適用してみた。
これはある動画のチャット欄から単語として有用な名詞、動詞、形容詞を抽出し、出現数をカウントしたものである。
(ちなみにあんかけパスタを食べているシーンのチャット200件を対象としている)

未適用の状態と比較すると「名古屋めし」や「あんかけパスタ」といういかにもな単語が、ちゃんと1ワードになっていることが確認できた。
2018年版と2020年版は特異な単語が無かったせいか大きな変化は無かった。
よって、Webの情報を処理する際には、NEologdを使用したほうが効果的であろう。

neologd_test_diff.png

結論

kuromojiNEologdを使ってどんどん形態素解析してこ!

あとがき:

nodeでゴリゴリ書けるのは非常に助かる。
やろうと思えばブラウザでも実行できるのは強い。
Sudachiってのも試してみたいね。

ぶっっっちゃけスクリプトをnodeで書き直せばこんなに引っかからないよねぇ。
けど、元ソースを読み解くのも面倒なので、環境側を合わせる方針にした。

...WSLを使うのも手か?(まだ触ってないから未知の世界)

bcryptでプレーンテキストをハッシュ化させる

$
0
0

ハッシュ化のメリット

・パスワードなどをデータベースに保存する時、プレーンテキストのまま保存すると脆弱性に繋がる。

・ハッシュ化したメッセージダイジェストから、元のメッセージを復元することは困難
→「インクリプション」…元のメッセージをとっておいて、それをハッシュ化したら同じハッシュ値になるかチェックすることで、同一性を確認する仕組み

bcryptパッケージ

bcryptというnpmパッケージを利用すると、プレーンテキストをハッシュ化することができる。

・bcryptパッケージの使い方
constbcrypt=require('bcryptjs')constmyFunction=async()=>{constpassword="password1234"consthadhedPassword=awaitbcrypt.hash(password,8)//第一引数:ハッシュ化したい値、第二引数:roundをかける回数。公式推奨は8回console.log(password)//元のパスワードを表示console.log(hassedPassword) //ハッシュ化されたパスワードを表示//元のパスワードとハッシュ化されたパスワードを比較し、同じものであるか確認constisMarch=awaitbcrypt.compare('password',hashedPassword)}myFunction()

値をハッシュ化させてからデータベースに組み込む

●Middlewareを利用してMongooseの振る舞い方をカスタマイズできる

Mongoose公式ドキュメント
「Middleware > Save/Validate」の項目
スクリーンショット 2020-08-04 22.35.59.png

・validateする前、または後にイベントを設定できる。
・saveする前、または後にイベントを設定できる。

→save()する前に、データベースに保存したい値をハッシュ化させることができる。

●例)
データベースに格納する前にpasswordをハッシュ化

constmongoose=require('mongoose')constbcrypt=require('bcryptjs')constuserSchema=newmongoose.Schema({name:{type:String,//データ型の設定required:true,trim:true},password:{type:String,required:true,}})//middlewareを記述。saveする前にプレーンテキストをハッシュするuserSchema.pre('save',asyncfunction(next){//アローファンクションは使わない。このbindingは大事な役割を果たすものだからconstuser=thisconsole.log('Middleware is working')//ミドルウェアが動いているか確認するためのテストコードif(user.isModified('password')){ //ハッシュ化されていないパスワードがある時user.password=awaitbcrypt.hash(user.password,8)}//ここで値がハッシュされるnext()})

toJSONを用いて、JSONデータの返り値を制御する

$
0
0

JSONデータの返り値を制御する

例)petというJSONデータの場合

constpet={name:'Doggy'}console.log(JSON.stringify(pet))


{'name':'Doggy'}
というJSONデータが返ってくる。

★返り値となるJSONデータを、toJSONを使って制御することができる

constpet={name:'Doggy'}pet.toJSON=function(){return{}}console.log(JSON.stringify(pet))


{}
というJSONデータが返ってくる。

つまり

toJSONで上書きされた値が、pet定数のJSONデータとして認識される

データベースのセキュリティ向上に利用

//mongooseを利用して、データベース型を設定constmongoose=require('mongoose')constuserSchema=newmongoose.Schema({name:{type:String,//データ型の設定required:true,trim:true},password:{type:String,required:true,},tokens:[{token:{type:String,require:true}}],})//toJSONを用いて、外からuserObjectにアクセスした時に、返すメソッドと返さないメソッドを決めるuserSchema.methods.toJSON=function(){constuser=thisconstuserObject=user.toObject()deleteuserObject.passworddeleteuserObject.tokensreturnuserObject}


これで、userObjectは、toJSONで指定されている、passwordとtoken以外のデータを返すようになる。

WiiリモコンをNode.jsから操ってみよう

$
0
0

Wiiリモコンは、中古で入手しやすく、機能も豊富なので、入力デバイスとしてはうってつけです。
接続もBluetoothなので、プロトコルさえわかれば、操れそうです。

ことの発端は、かの神モジュール「noble」を勉強のためソースコードを見ていたのですが、自分でも操ってみようと思い、そこで思いついたのがWiiリモコンでした。

毎度の通り、ソースコードもろもろを、GitHubに上げておきます。

poruruba/WiiRemocon
 https://github.com/poruruba/WiiRemocon

※たぶん、Linuxでしか動かないと思います。

Wiiリモコンのプロトコル

ここにすべて書いてあります!

WiiBrew:Wiimote
 http://wiibrew.org/wiki/Wiimote

Wiiリモコンとは、BluetoothのL2CAPプロトコルで通信します。
HIDとして見えるので、PSM=0x0011(HID Control)とPSM=0x0013 (HID Interrupt)の2つのコネクションを張る必要があります。

L2CAPプロトコルの接続には、Linuxのsocket関数を使うのですが、node-gypでネイティブ実装しました。
こちらを参考にさせていただきました。あと、node-bluetooth-hci-socketも。

 Node.jsのネイティブ拡張を作ってみよう 〜NAN, 非同期処理, npm公開まで〜

本来であれば、HCI Commandの「Create Connection Command」や、L2CAPの「Connection Request」や「Configuration Request」の処理をする必要がありますが、socket関数が内部で処理してくれます。

接続した後は、HID Interruptの通信路に、Wiiリモコンの通信データ(HIDのレポート)が永遠と飛んできます。

Wiiリモコンの発見

Wiiリモコンは、①と②のマークのボタンを同時に押すとLEDが点滅して、Discoveryモードになって発見できる状態になります。

発見には、BluetoothのHCI Command Packetを使います。そのレイヤの操作に以下のモジュールを使っています。nobleの中でnpmモジュール化していただいているものです。

noble/node-bluetooth-hci-socket
 https://github.com/noble/node-bluetooth-hci-socket

Bluetoothをご存じの方であれば、以下のコマンドとイベントを使います。
・Inquiry Command(OCF=0x0001)
・Inquiry Complete Event(Event Code=0x01)
・Inquiry Result Event(Event Code=0x02)

Wiiリモコンの操作ライブラリ

うーん、今回もGitHubを見てもらった方がよいかなあ。(最近手抜きが多い。。。)
inquiry.js というファイルです。

以下の2つのnpm モジュールを利用しています。
・bluetooth-hci-socket
・debug

BLUETOOTH SPECIFICATION
 7 HCI COMMANDS AND EVENTS
 7.1 LINK CONTROL COMMANDS

 7.7 EVENTS
の辺りを見れば、大体わかります。

パケットフォーマットは、
 Figure 5.1 HCI Command Packet
にあります。ただし、socket関数を使う場合は、先頭1バイトに0x01を入れる必要があるようです。

使い方は以下の感じ。
ただし、これの実行には、ルート権限が必要です。ルート権限不要としたい場合は以下を参照してください。
 https://github.com/noble/noble#running-without-rootsudo

inquiry_test.js
constInquery=require('./inquiry');constinquiry=newInquery();asyncfunctioninquiry_device(){returnnewPromise((resolve,reject)=>{varlocal_address=null;varremote_address=null;inquiry.on("initialized",(address)=>{console.log("local: "+address);local_address=address;inquiry.inquiry(10,1);});inquiry.on("inquiryResult",(address)=>{console.log("remote: "+address);remote_address=address;});inquiry.on("inquiryComplete",(status)=>{console.log("status: "+status);inquiry.stop();resolve({local:local_address,remote:remote_address});});inquiry.init();})}inquiry_device().then(result=>{console.log(result);}).catch(error=>{console.error(error);});

Wiiリモコン操作用のライブラリ

ネイティブライブラリの力を借ります。まず、準備。

$ npm install -g node-gyp
$ npm install nan

node-gypの設定ファイルを作成します。

building.gyp
{"targets":[{"target_name":"binding","sources":["src/BtL2capHid.cpp"],'link_settings':{'libraries':['-lbluetooth',],},"include_dirs":["<!(node -e \"require('nan')\")"]}]}

以下のように準備して、コンパイル

$ node-gyp configure
$ node-gyp build

またしても、ソース割愛。BtL2capHid.cppというファイルです。

やっていることは、
・Node.jsとC言語の呼び出しの橋渡し
・socket.connectで、L2CAPプロトコルの接続(2つのPSM)
・socket.readでブロッキングモードで受信待ちしていったん関数戻り、受信したらコールバック呼び出し

これで、build\Release\binding.nodeというのが出来上がります。
後はこれを使いやすいように、jsファイルでくるみます。受信呼び出しを繰り返し呼ばないといけないように作っています。

wiiremocon.js
'use strict';varEventEmitter=require('events').EventEmitter;varbinding=require('./build/Release/binding.node');constWIIREMOTE_RUMBLE_MASK=0x01;constWIIREMOTE_LED_MASK=0xf0;classWiiRemoconextendsEventEmitter{constructor(){super();this.WIIREMOTE_LED_BIT0=0x80;this.WIIREMOTE_LED_BIT1=0x40;this.WIIREMOTE_LED_BIT2=0x20;this.WIIREMOTE_LED_BIT3=0x10;this.cur_rumble_led=0x00;this.l2cap=newbinding.BtL2capHid();}addr2bin(address){returnBuffer.from(address.split(':').reverse().join(''),'hex');}addr2str(address){returnaddress.toString('hex').match(/.{1,2}/g).reverse().join(':');}connect(addr,retry=2){console.log('connect');returnnewPromise((resolve,reject)=>{this.l2cap.connect(addr,retry,(err,result)=>{if(err)returnreject(err);this.startRead();resolve(result);});})}asyncreadAsync(){returnnewPromise((resolve,reject)=>{this.l2cap.read((err,data)=>{if(err)returnreject(err);resolve(data);});});}startRead(){console.log('startRead');returnnewPromise(async(resolve,reject)=>{do{try{varresult=awaitthis.readAsync();this.emit("data",result);}catch(error){console.error(error);returnresolve(error);}}while(true);});}setReport(id,value){console.log('setReport called');varparam=Buffer.alloc(3);param.writeUInt8(0xa2,0);param.writeUInt8(id,1);param.writeUInt8(value,2);console.log('setReport:'+param.toString('hex'));returnthis.l2cap.write(0,param);}setLed(led_mask,led_val){this.cur_rumble_led=(this.cur_rumble_led&~(led_mask&WIIREMOTE_LED_MASK))|(led_val&WIIREMOTE_LED_MASK);returnthis.setReport(0x11,this.cur_rumble_led);}setRumble(rumble){this.cur_rumble_led=(this.cur_rumble_led&~WIIREMOTE_RUMBLE_MASK)|(rumble&WIIREMOTE_RUMBLE_MASK);returnthis.setReport(0x11,cur_rumble_led);}setDataReportingMode(mode){varparam=Buffer.alloc(4);param.writeUInt8(0xa2,0);param.writeUInt8(0x12,1);param.writeUInt8(0x00,2);param.writeUInt8(mode,3);console.log('setDataReportingMode:'+param.toString('hex'));returnthis.l2cap.write(0,param);}}module.exports=WiiRemocon;

あとは、こんな感じで使います。
node起動時に、引数にWiiリモコンのBluetoothのMacアドレスを指定します。「XX:XX:XX:XX:XX:XX」という形式です。

wiiremocon_test.js
constWiiRemocon=require('./wiiremocon');varwii=newWiiRemocon();asyncfunctionwiiremote_monitoring(remote_address){wii=newWiiRemocon();wii.on("data",data=>{console.log(data);});awaitwii.connect(wii.addr2bin(remote_address));wii.setLed(wii.WIIREMOTE_LED_BIT0|wii.WIIREMOTE_LED_BIT1|wii.WIIREMOTE_LED_BIT2|wii.WIIREMOTE_LED_BIT3,0);}wiiremote_monitoring(process.argv[2]).catch(error=>{console.error(error);});

wii.on(“data”, function(data)) のところに、Wiiからボタン等の状態が送られてきます。
ボタンを押したときにイベントデータが送られてきますが、setDataReportingModeで例えば0x31を指定してモードを変更すれば、加速度などがひっきりなしに送られてきます。

終わりに

あとは、Node.js上でいろいろいじれそうです。
WiiヌンチャクやWii Fitボードなども試してみようと思います。

以上


Google Functions & Node.js: console.logを使った最低限のロギング

$
0
0

この投稿では、Google Cloud Platform(GCP)のGoogle Cloud Functions(GCF)のNode.js環境で、console.logを使った最低限のロギング手法について解説します。

この投稿で学ぶこと

  • console.logで最低限のロギングは可能。
  • 2行以上に渡るログは行ごとに分解されるので注意。
  • jsonPayloadを意識すると、オブジェクトの構造をログに出すことも可能。

GCFではconsole.logでログを残せて、「ログビューア」で確認できる

まず、GCFでどのようにロギングし、そのログをどうやって確認するのかを学びます。シンプルに1行のメッセージをconsole.logで記録してみましょう。

index.js
exports.helloWorld=(req,res)=>{console.log('helloWorld関数を実行しています。')res.send('Hello World!')}

このhelloWorld関数をGCPにデプロイします:

gcloud functions deploy helloWorld --runtime=nodejs12 --trigger-http

デプロイが完了したら、curlで関数を呼び出してみます:

curl https://asia-northeast1-${PROJECT}.cloudfunctions.net/helloWorld

どのようなログが出たかは、GCPの管理コンソールの「Cloud Functions」を開き、「helloWorld」関数のメニューの「ログを表示」を開きます。

CleanShot 2020-08-05 at 10.45.30@2x.png

開くと「ログビューア」が表示され、「クエリ結果」の部分にログの内容が表示されます:

CleanShot 2020-08-05 at 10.49.09@2x.png

ログが表示されるエリアを拡大してみると、console.logでロギングした「helloWorld関数を実行しています。」が記録されていることが分かります:

CleanShot 2020-08-05 at 10.51.40@2x.png

:bulb:関数が実行されてからログビューアに反映されるまで、数十秒の遅延があります。なので、関数実行後すぐにログビューアを開いてもログが出ていないかもしれません。その場合は、ログが出るまでログビューアのクエリ結果にある「現在の位置に移動」ボタンをクリックして新しいログの到着を待ちましょう。

ログは>のつまみを押すと、メタ情報を見ることができます:

CleanShot 2020-08-05 at 10.57.42@2x.png

本稿では触れませんが、これらのメタ情報を活用してログを分析したりすることができます。

Node.js環境のGCFでは、console.logを使えば特にGCP側の設定をいじらなくてもログを確認できることが分かりました。

console.logで2行以上出す場合は、ログが分割されてしまうので注意

console.logは複数行の文字列をロギングすることができますが、GCPで複数行のロギングをする場合は、ログが分割されてしまうので注意が必要です。どういうことか実験して確認してみましょう。

次の関数は複数行のログを吐くものです:

index.js
exports.helloWorld=(req,res)=>{console.log('1行目\n2行目\n3行目\n4行目\n5行目')res.send('Hello World!')}

これをデプロイして呼び出してみると、ログビューアには次のようなログが残ります:

CleanShot 2020-08-05 at 11.05.04@2x.png

見ての通り、1回のconsole.logなのに、ログは5つ出来上がっています。console.logごとに1つログができると思っていると、複数行になった場合、予想外のログになるので注意しましょう。

上の例だと、ログが複数行になっても問題ないですが、困る場合もあります。例えば、下の例のようにオブジェクトをconsole.logすると、

exports.helloWorld=(req,res)=>{console.log({boolean:true,number:1,string:'string',array:[1,2,3],object:{field1:'aaaaaaaaaaaa',field2:'aaaaaaaaaaaa',field3:'aaaaaaaaaaaa',},})res.send('Hello World!')}

ログがバラバラになってしまいビューアでの可読性が良くありませんし、ログをコピペするのも一手間だったりと、運用上の面倒くささが出てきます:

CleanShot 2020-08-05 at 11.14.04@2x.png

この例では再現しませんでしたが、ログ行の順番が前後してしまうケースもあったりして、オブジェクトのようなネストした構造を安心して確認できないという問題もあったりします。

オブジェクトをconsole.logするときは一旦JSONにすると、1ログになり、構造化もされる

複数行のログ、特にオブジェクトをconsole.logするときは、そのオブジェクトを一旦JSONにするといいです。JSONのログはGCPが特別扱いしてくれるので、ログが複数に分かれることが避けられ、おまけに、ログビューアでは構造化されて表示されるので見やすさも向上します。

例えば、下の関数のように、JSON.stringifyでオブジェクトをJSON化した上で、console.logするようにします:

exports.helloWorld=(req,res)=>{console.log(JSON.stringify({boolean:true,number:1,string:'string',array:[1,2,3],object:{field1:'aaaaaaaaaaaa',field2:'aaaaaaaaaaaa',field3:'aaaaaaaaaaaa',},}),)res.send('Hello World!')}

この関数を実行し、そのログを確認すると1行のログにまとまっていることが分かります:

CleanShot 2020-08-05 at 11.21.59@2x.png

加えて、ログを開いてみると、jsonPayloadフィールドにオブジェクトが構造化されているのが分かります:

CleanShot 2020-08-05 at 11.23.34@2x.png

JSON.stringifyでデバッグできないオブジェクトもあるので注意

JSON.stringifyすればどんなオブジェクトもデバッグできるかというと、そうでもないので注意してください。例えば、SetMapはJSON化すると{}になってしまいます:

constmap=newMap([['a',1],['b',2]])constset=newSet([1,2,3])console.log(map)//=> Map(2) { 'a' => 1, 'b' => 2 }console.log(set)//=> Set(3) { 1, 2, 3 }console.log(JSON.stringify(map))//=> {}console.log(JSON.stringify(set))//=> {}

こうしたJSON化時に情報が失われるオブジェクトのロギングをGCPでどうやったらいいか、そのベストプラクティスは僕も分かっていません。もし、ご存じの方がいましたら教えてください。

まとめ

  • 特に何も設定せずともNode.jsならconsole.logで最低限のロギングは可能。
  • 2行以上に渡るログは行ごとに分解されるので注意。
  • jsonPayloadを意識したロギングをすれば、オブジェクトの構造をログに出すことも可能。

Google Functions: console.infoやconsole.errorなどとログビューアの「重大度」の関係性

$
0
0

JavaScriptのConsole APIには、ロギングで良く使うconsole.log以外に、console.infoconsole.errorなど、ログに「情報」や「エラー」といった色をつけるメソッドがあります。

一方、Google Cloud Platform(GCP)のログビューアの「重大度(SEVERITY)」という概念があり、ログごとに「INFO」や「ERROR」などの意味合いを持たせることができます。

では、Console APIとGCPの「重大度」はどのような関係になっているのでしょうか? 実験してみたので、この投稿ではその結果をお伝えしたいと思います。

結論

先に結論を示します。JavaScriptのConsole APIのメソッドの違いは、基本的にGCPの重大度に影響しません。ただし、console.warnconsole.errorErrorオブジェクトをロギングした場合に限り、重大度が「ERROR」になります。

Errorオブジェクト以外をロギングした場合

Console APIGCPの重大度
console.logDEFAULT
console.infoDEFAULT
console.warnDEFAULT
console.errorDEFAULT

Errorオブジェクトをロギングした場合

Console APIGCPの重大度
console.logDEFAULT
console.infoDEFAULT
console.warnERROR
console.errorERROR

console.infoやconsole.errorなどが、ログビューアでどの「重大度」になるか検証する

各種メソッドを検証するために、次のような関数を用意しました:

index.js
exports.logging=(req,res)=>{console.log('テキストをconsole.log')console.info('テキストをconsole.info')console.warn('テキストをconsole.warn')console.error('テキストをconsole.error')console.log(newError('Errorオブジェクトをconsole.log'))console.info(newError('Errorオブジェクトをconsole.info'))console.warn(newError('Errorオブジェクトをconsole.warn'))console.error(newError('Errorオブジェクトをconsole.error'))res.send('OK')}

これをデプロイして、

gcloud functions deploy logging --runtime=nodejs12 --trigger-http

実行してみます:

curl https://asia-northeast1-${PROJECT}.cloudfunctions.net/logging

すると、ログビューアには次のようなログが残りました:

CleanShot 2020-08-05 at 12.10.12@2x.png

この結果を確認すると、console.warnconsole.errorErrorオブジェクトをロギングした場合は、重大度がERRORになり、それ以外はDEFAULTになったことが分かります。

Electronをバージョンアップしたらwindowsで4G以上のファイルが読み込めなくなった話

$
0
0

electron6系から8系へバージョンアップした際に、4G以上の大きいサイズのファイルにおいて、ビルド後ローカルファイルが読み込めなくなりました(windowsのみ。ローカル起動では可能)
Mac OSでは読み込みが可能でした
どこにも情報が無く数週間悩んだのでまとめてみました

環境

  • electron バージョン: electron 8.3.1
  • OS: windows10 64bit
  • ビルド: electron-builder 22.1.0

XMLHttpRequest (XHR) 通信でローカルファイル読み込み

期待する値

  • readyState=4(DONE)になる
  • onloadイベントが発火

実際の値

  • readyState=3(LOADING)のまま
  • onprogress でずっと読み込み中 (totalの値まで到達してもonloadが呼ばれない)
  • ontimeoutも発火せず...

対応

  • 32bit アプリを 64bit に変更(electron-builderにて、ビルドの引数を変更した)
    • 「electron-builder build --win --ia32」 -> 「npm run obfuscation && electron-builder build --win --x64」

補足(XMLHttpRequestで通信エラーになっているときの判定)

  • totalフィールド: 全転送バイト数
  • loadedフィールド: これまで転送されたバイト数
  • 通信エラーになっている時以下になっている(全体の長さが不明の状態)
    • total = 0
    • lengthComputable = false
xhr.js
// データ受信中のイベントvarxhr=newXMLHttpRequest();xhr.onprogress=function(oEvent){if(oEvent.lengthComputable||xhr.total!==0){varpercentComplete=oEvent.loaded/oEvent.total*100;// ...}else{// 全体の長さが不明なため、進捗情報を計算できない}

参考

Bot Framework v4 (node.js) をイチから学ぶ (1) オウム返しができるまで

$
0
0

今更ながら、Bot Framework v4 (node.js) を やりたいことを実装できることを目標に、イチから学んでみることにしました。

執筆時点のバージョン:

  • Bot Framework (javascript): v4.9.3
  • node.js: v12.16.3
  • npm: v6.14.4

※ Bot Framework SDK (C#) はある程度触っている & node.js はそれほど詳しくないため、分かりにくいところや間違っているところあればご指摘ください。(プルリク的に模範解答を添えて修正依頼をくださると助かります)

Bot Framework v4 (node.js) をイチから学ぶ シリーズ

必要なコード

EchoBot
+- bot.js          // Chatbot としての挙動
+- index.js        // API としての基本動作
+- package.json    // 必要なライブラリーや依存関係など
+- .env       // 環境変数を設定

Bot Framework の Bot Builder Samplesにある EchoBotから上記ファイルをダウンロードするなどして入手、編集するベースとすれば OK です。

手順

チャットボットの稼働に必要なライブラリーやファイルを確認する

node.js の実行ファイルとして index.js が設定されていますので、こちらで必要なライブラリーやファイルを確認しておきます。(※編集作業ナシ)

ライブラリーはBot Framework を使う上で必要となる botbuilderの他、restify, dotenv, pathが必要です。
Azure Bot Service の公開に必要な ID などは .envで設定します。
チャットボットの挙動を記述する EchoBot (ファイル名は bot.js) を読み込んでいます。

index.js
constpath=require('path');constrestify=require('restify');// Import required bot services.// See https://aka.ms/bot-services to learn more about the different parts of a bot.const{BotFrameworkAdapter}=require('botbuilder');// This bot's main dialog.const{EchoBot}=require('./bot');// Read environment variables from .env fileconstdotenv=require('dotenv');// Import required bot configuration.constENV_FILE=path.join(__dirname,'.env');dotenv.config({path:ENV_FILE});// Create adapter.// See https://aka.ms/about-bot-adapter to learn more about how bots work.constadapter=newBotFrameworkAdapter({appId:process.env.MicrosoftAppId,appPassword:process.env.MicrosoftAppPassword});:(後略)

チャットボット本体は Web API で、ローカルで実行した場合は localhost:3978/api/messages で待ち受けます。
EchoBot から bot インスタンスを作成し、HTTP リクエストがあった場合に作動します。

index.js
(前略):// Create HTTP serverconstserver=restify.createServer();server.listen(process.env.port||process.env.PORT||3978,()=>{console.log(`\n${server.name} listening to ${server.url}`);console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator');console.log('\nTo talk to your bot, open the emulator select "Open Bot"');});:(中略):// Create the main dialog.constbot=newEchoBot();// Listen for incoming requests.server.post('/api/messages',(req,res)=>{adapter.processActivity(req,res,async(context)=>{// Route to main dialog.awaitbot.run(context);});});:(後略)

チャットボットを起動する

コマンドラインから npm installで必要なライブラリーをインストールします。
その後、 node index.js、Package.json をサンプルからそのままで使っている場合は npm startで index.js が起動します。

01.jpg

Bot Framework Emulator から https://localhost:3978/api/messagesにアクセスして挙動を確認します。

02.jpg

EchoBotをそのまま使っている場合は、以下のように初期メッセージと、ユーザー入力に対するオウム返しが返答されます。

03.jpg

ユーザーからメッセージが来たら返信する

ユーザーからのメッセージ (HTTPリクエスト) に対する動作は EchoBot (ファイル名は bot.js) に記述します。

  • Activity: ユーザーの接続やメッセージの受信&返信を含む、すべての動作(アクティビティ)
  • TurnContext: ボットの制御(とその情報)。ユーザーとの受信、返信の内容を履歴として保持しています

ユーザーの動作は Activity で取得することができ、メッセージを受信したときは ActivityHander.onMessage が参照されます。
ActivityHander で onMessage を検知したとき、TurnContext.sendActivity メソッドで返信することができます。

ここではユーザーからメッセージを受信したとき、「hello!」と返信する処理のみを記述するように、既存の bot.jsを修正します。next() でユーザーからの受信を待ちます;

bot.js
const{ActivityHandler}=require('botbuilder');classEchoBotextendsActivityHandler{constructor(){super();// メッセージを受信したときthis.onMessage(async(turnContext,next)=>{// 返信して返答を待つawaitturnContext.sendActivity('hello!');awaitnext();});}}module.exports.EchoBot=EchoBot;

起動して Emulator から動作を確認します。

11.jpg

ユーザーからのメッセージ内容を取得する

TurnContext にユーザーとの受信、返信情報を保持しており、直前のユーザーからのメッセージは TurnContext.activity プロパティから取得できます。text(テキストメッセージ) または attatchment((あれば)添付ファイル) が取得できます。
取得したメッセージをそのままオウム返しするように記述します。

bot.js
const{ActivityHandler}=require('botbuilder');classEchoBotextendsActivityHandler{constructor(){super();this.onMessage(async(turnContext,next)=>{// ユーザーのメッセージ内容を取得して、おうむ返し(Echo)の返答を送信するconstreplyText=turnContext.activity.text+'って言ったね!';awaitturnContext.sendActivity(replyText);awaitnext();});}}module.exports.EchoBot=EchoBot;

12.jpg

Welcome メッセージを送信する

ActivityHander.onMembersAdded でユーザーが接続したことを検出できるので、その際にメッセージを送信する動作を追加します。(※接続したユーザークライアントによりできない場合アリ)

bot.js
const{ActivityHandler}=require('botbuilder');classEchoBotextendsActivityHandler{constructor(){super();this.onMembersAdded(async(turnContext,next)=>{awaitturnContext.sendActivity('はじめまして!');awaitnext();});this.onMessage(async(turnContext,next)=>{constreplyText=turnContext.activity.text+'って言ったね!';awaitturnContext.sendActivity(replyText);awaitnext();});}}module.exports.EchoBot=EchoBot;

13.jpg

Bot Framework v4 (node.js) をイチから学ぶ (2) Dialog で処理をブロック化して呼び出し実行

$
0
0

Bot Framework v4 (node.js) をイチから学ぶ シリーズ Top

チャットボットがユーザーとやり取りを行う動作、タスクは Dialogでブロック化し、(再)利用することができます。実行している Dialog のステート(状態) は MemoryStorageという領域に保存します。

  • Dialog: タスクを実行するファンクション(関数)
  • ComponentDialog: Dialog の実行順序設定、読み込み、実行&コントロールするクラスモジュール
  • MemoryStorage: Dialog のステートや、会話に必要な情報を保存する領域
    • ConversationState: 会話の状態 (どのダイアログにいるのか) など、メッセージの受信&返信を会話として進める上で必要な情報
    • UserState: ConversationState 以外のユーザー情報など

必要なコード

DialogBot
+- mainDialog.js   // チャットボットのタスクを切り出した ComponentDialog (親)
+- subDialog.js    // チャットボットのタスクを切り出した ComponentDialog (子)
+- dialogBot.js    // Chatbot としての挙動
+- index.js        // API としての基本動作
+- package.json    // 必要なライブラリーや依存関係など
+- .env       // 環境変数を設定

手順

ステート保存領域を生成する

MemoryStorage を作成、その配下にステートを含む会話に必要な情報を保存します。

ConversationState で Dialog のステートを管理する

ひとまず MemoryStorage を定義して、ConversationState を生成します。
MainDialog という ComponentDialog (ファイル名は mainDialog.js) を作成し、こちらでタスクを実行します。
Bot インスタンスが起動するときに ComversationState および MainDialog を取得するようにします。

index.js
constrestify=require('restify');constpath=require('path');// BotFrameworkAdapter に追加して、MemoryStorage と ConversationState をインポート// const { BotFrameworkAdapter } = require('botbuilder');const{BotFrameworkAdapter,MemoryStorage,ConversationState}=require('botbuilder');// Dialog を操作する js ファイル(dialogBot.js)を新たに作成、追加// const { EchoBot } = require('./bot');const{DialogBot}=require('./dialogBot');:(中略):// 空の MemoryStorage を作成して、その配下に ConversationState を生成するconstmemoryStorage=newMemoryStorage();constconversationState=newConversationState(memoryStorage);:(中略):// Dialog と ConversationState を DialogBot で利用する// const bot = new EchoBot();constdialog=newMainDialog();constbot=newDialogBot(conversationState,dialog);server.post('/api/messages',(req,res)=>{adapter.processActivity(req,res,async(context)=>{// Route to main dialog.awaitbot.run(context);});});

メッセージ応対を ComponentDialog で行うように設定する

dialogBot.jsでは、メッセージを受け取って返信する処理を ComponentDialog から行うように設定し、ConversationStateを使って Dialog のコントロールを行います。
ConversationState に DialogStateを保存し、DialogState に従って Dialog を実行するプロセスになります。

dialogBot.js
const{ActivityHandler}=require('botbuilder');classDialogBotextendsActivityHandler{constructor(conversationState,dialog){super();// Dialog の読み込みthis.dialog=dialog;// ConversationState から DialogState を取得this.conversationState=conversationState;this.dialogState=this.conversationState.createProperty('DialogState');// メッセージを受信したときthis.onMessage(async(turnContext,next)=>{// DialogState で指定された Dialog を実行、その次の Dialog をポイントawaitthis.dialog.run(turnContext,this.dialogState);awaitnext();});}// ステートを保存するため、ActivityHandler.run を Overrideasyncrun(turnContext){awaitsuper.run(turnContext);awaitthis.conversationState.saveChanges(turnContext,false);}}module.exports.DialogBot=DialogBot;

WaterfallDialog でタスクとその構成を記述する

ひとまず mainDialog.js内に実行したいタスク(Step)を記述していきます。
WaterfallDialogを使って、Step を実行したい順に記述します。step.next()で次の Step に送り、最後の Step で step.endDialog()を呼び出して、この WaterfallDialog を終了します。
また、MainDialog が呼び出された時に この WaterfallDialog を実行する手順も記述します。

mainDialog.js
const{ComponentDialog,DialogSet,WaterfallDialog,DialogTurnStatus}=require('botbuilder-dialogs');constMAIN_DIALOG='mainDialog';classMainDialogextendsComponentDialog{constructor(){// 現在の Dialog の ID を設定super(MAIN_DIALOG);// この Dialog 内で実行するタスク(Step)を列記this.addDialog(newWaterfallDialog('start',[async(step)=>{awaitstep.context.sendActivity('こんにちは!')// 次の Step に送るreturnstep.next();},async(step)=>{awaitstep.context.sendActivity('メインメニューです!')// 最終 Step で この WaterfallDialog を終了するreturnstep.endDialog();}]));this.initialDialogId='start';}asyncrun(turnContext,dialogState){constdialogSet=newDialogSet(dialogState);dialogSet.add(this);// WaterfallDialog を順に実行// 開始されていない場合は、initialDialogId に指定されている WaterfallDialog を実施constdialogContext=awaitdialogSet.createContext(turnContext);constresults=awaitdialogContext.continueDialog();if(results.status===DialogTurnStatus.empty){awaitdialogContext.beginDialog(this.id);}}}module.exports.MainDialog=MainDialog;

Dialog Prompt でユーザーとのやり取りを簡略に記述

予め用意されている Dialog Promptを利用すると、WaterfallDialog 内でユーザーへのメッセージ送信とユーザーのメッセージ取得を簡略に記述できます。

テキストを取得する TextPrompt, Yes|No を選ばせる ConfirmPrompt など、詳細はドキュメント↓を確認してください。
Microsoft Docs > Azure Bot Service > ダイアログライブラリ - プロンプト

作成した Dialog Prompt は step.prompt(オブジェクト名)で呼び出します。step.prompt() には step.next() の処理が含まれています。

mainDialog.js
// TextPrompt, NumberPrompt, ConfirmPrompt を追加const{ComponentDialog,DialogSet,WaterfallDialog,DialogTurnStatus,TextPrompt,NumberPrompt,ConfirmPrompt}=require('botbuilder-dialogs');// 作成する Dialog Prompt のオブジェクト名称を設定constMAIN_DIALOG='mainDialog';constNAME_PROMPT='namePrompt';constYESNO_PROMPT='yesnoPrompt';constAGE_PROMPT='agePrompt';classMainDialogextendsComponentDialog{constructor(){// 現在の Dialog の ID を設定super(MAIN_DIALOG);// 利用したい Dialog Prompt を新規作成して追加this.addDialog(newTextPrompt(NAME_PROMPT));this.addDialog(newConfirmPrompt(YESNO_PROMPT));this.addDialog(newNumberPrompt(AGE_PROMPT));// この Dialog 内で実行するタスク(Step)を列記this.addDialog(newWaterfallDialog('start',[async(step)=>{awaitstep.context.sendActivity('こんにちは!')// 次の Step に送るreturnstep.next();},// Dialog Prompt で記述async(step)=>{returnawaitstep.prompt(NAME_PROMPT,'あなたの名前は?');},async(step)=>{// ユーザーからのメッセージ(入力値) step.result を取得、step.values の一時領域に保存step.values.name=step.result;returnawaitstep.prompt(YESNO_PROMPT,'あなたの年齢を聞いてもよいですか?',['はい','いいえ']);},async(step)=>{if(step.result){returnawaitstep.prompt(AGE_PROMPT,'では、あなたの年齢を入力してね');}else{// いいえ(false) の場合は、-1 を値として設定returnawaitstep.next(-1);}},async(step)=>{if(step.result>=20){awaitstep.context.sendActivity(step.values.name+'さん、あなたはお酒が飲めますね!');}awaitstep.context.sendActivity('メインメニューです!')// 最終 Step で この WaterfallDialog を終了するreturnstep.endDialog();}]));this.initialDialogId='start';}:(後略)

WaterfallDialog の実行 Step を ComponentDialog のメソッドとして独立させる

WaterfallDialog に記述している Step をこの ComponentDialog (MainDialog) 自体のメソッドとして独立させます。

mainDialog.js
(前略):classMainDialogextendsComponentDialog{constructor(){// 現在の Dialog の ID を設定super(MAIN_DIALOG);// 利用したい Dialog Prompt を新規作成して追加this.addDialog(newTextPrompt(NAME_PROMPT));this.addDialog(newConfirmPrompt(YESNO_PROMPT));this.addDialog(newNumberPrompt(AGE_PROMPT));// この Dialog 内で実行するタスク(Step)を列記this.addDialog(newWaterfallDialog('start',[this.initialStep.bind(this),this.nameAskStep.bind(this),this.ageComfirmStep.bind(this),this.ageAskStep.bind(this),this.finalStep.bind(this)]));this.initialDialogId='start';}// 実行するタスク(Step)asyncinitialStep(step){awaitstep.context.sendActivity('こんにちは!')// 次の Step に送るreturnstep.next();}asyncnameAskStep(step){returnawaitstep.prompt(NAME_PROMPT,'あなたの名前は?');}asyncageComfirmStep(step){step.values.name=step.result;returnawaitstep.prompt(YESNO_PROMPT,'あなたの年齢を聞いてもよいですか?');}asyncageAskStep(step){if(step.result){returnawaitstep.prompt(AGE_PROMPT,'では、あなたの年齢を入力してね');}else{// いいえ(false) の場合は、-1 を値として設定returnawaitstep.next(-1);}}asyncfinalStep(step){if(step.result>=20){awaitstep.context.sendActivity(step.values.name+'さん、あなたはお酒が飲めますね!');}awaitstep.context.sendActivity('メインメニューです!')// 最終 Step で この WaterfallDialog を終了するreturnstep.endDialog();}:(後略)

WaterfallDialog の実行 Step を個別の ComponentDialog として独立させる

WaterfallDialog の実行 Step を別の ComponentDialog として独立させて利用します。
独立させる subDialog.jsmainDialog.jsとほぼほぼ同じ、Dialog 名を SUB_DIALOG(subDialog) に設定する箇所だけ変更しています。(もちろん、各 Step を ComponentDialog のメソッドとして独立させていなくても良いです。)

subDialog.js
const{ComponentDialog,DialogSet,WaterfallDialog,DialogTurnStatus,TextPrompt,NumberPrompt,ConfirmPrompt}=require('botbuilder-dialogs');constSUB_DIALOG='subDialog';constNAME_PROMPT='namePrompt';constYESNO_PROMPT='yesnoPrompt';constAGE_PROMPT='agePrompt';classSubDialogextendsComponentDialog{constructor(){super(SUB_DIALOG);this.addDialog(newTextPrompt(NAME_PROMPT));this.addDialog(newConfirmPrompt(YESNO_PROMPT));this.addDialog(newNumberPrompt(AGE_PROMPT));this.addDialog(newWaterfallDialog('start',[this.initialStep.bind(this),this.nameAskStep.bind(this),this.ageComfirmStep.bind(this),this.ageAskStep.bind(this),this.finalStep.bind(this)]));this.initialDialogId='start';}// 実行するタスク(Step)asyncinitialStep(step){awaitstep.context.sendActivity('こんにちは!')// 次の Step に送るreturnstep.next();}asyncnameAskStep(step){returnawaitstep.prompt(NAME_PROMPT,'あなたの名前は?');}asyncageComfirmStep(step){step.values.name=step.result;returnawaitstep.prompt(YESNO_PROMPT,'あなたの年齢を聞いてもよいですか?');}asyncageAskStep(step){if(step.result){returnawaitstep.prompt(AGE_PROMPT,'では、あなたの年齢を入力してね');}else{// いいえ(false) の場合は、-1 を値として設定returnawaitstep.next(-1);}}asyncfinalStep(step){if(step.result>=20){awaitstep.context.sendActivity(step.values.name+'さん、あなたはお酒が飲めますね!');}awaitstep.context.sendActivity('メインメニューです!')// 最終 Step で この WaterfallDialog を終了するreturnstep.endDialog();}asyncrun(turnContext,accessor){constdialogSet=newDialogSet(accessor);dialogSet.add(this);constdialogContext=awaitdialogSet.createContext(turnContext);constresults=awaitdialogContext.continueDialog();if(results.status===DialogTurnStatus.empty){awaitdialogContext.beginDialog(this.id);}}}module.exports.SubDialog=SubDialog;

mainDialog.js側は、WaterfallDialog で利用する Dialog に SubDialog() を追加し、step.beginDialog()を使って呼び出します。もちろんメソッドに切り出して、呼び出してもOKです。

mainDialog.js
const{ComponentDialog,DialogSet,WaterfallDialog,DialogTurnStatus}=require('botbuilder-dialogs');const{SubDialog}=require('./subDialog');constMAIN_DIALOG='mainDialog';constSUB_DIALOG='subDialog';classMainDialogextendsComponentDialog{constructor(){// 現在の Dialog の ID を設定super(MAIN_DIALOG);// WaterfallDialog で実行する Dialog を定義this.addDialog(newSubDialog());// この Dialog 内で実行するタスク(Step)を列記this.addDialog(newWaterfallDialog('start',[async(step)=>{returnawaitstep.beginDialog(SUB_DIALOG)}]));this.initialDialogId='start';}asyncrun(turnContext,dialogState){constdialogSet=newDialogSet(dialogState);dialogSet.add(this);// WaterfallDialog を順に実行// 開始されていない場合は、initialDialogId に指定されている WaterfallDialog から実施constdialogContext=awaitdialogSet.createContext(turnContext);constresults=awaitdialogContext.continueDialog();if(results.status===DialogTurnStatus.empty){awaitdialogContext.beginDialog(this.id);}}}module.exports.MainDialog=MainDialog;

Google Functions: Node.jsで重要度付きのロギング

$
0
0

前回、Google Functions: console.infoやconsole.errorなどとログビューアの「重大度」の関係性という記事を投稿しました。そこではconsole.errorconsole.infoなどのConsole APIでは、GCP上のログの重要度(severity)は、DEFAULTERRORの二択になるということを説明しました。

GCP上の重要度はこの2つしか無いわけではなく、以下の9つのレベルがあります。

LogEntry_ _ _Cloud_Logging_ _ _Google_Cloud.png

この投稿では、Google Cloudのロギングクライアントライブラリを使って、Cloud Functionsでも重要度を指定したロギングをする方法を説明します。

この投稿で学ぶこと

  • Google Cloud Function & Node.jsで@google-cloud/loggingを使って重要度をつけたログを記録する方法
  • そして、その面倒くささ。
  • console.logと@google-cloud/loggingで記録されるログ内容の違い。

ロギングクライアントライブラリをインストールする

まず必要となるロギングクライアントライブラリをインストールします。

yarn add @google-cloud/logging

ライブラリをCloud Functionsに組み込む

次にこのロギングライブラリをCloud Functionsの実装に組み込みます。

下のコードが組み込んだものです。console.logを使ってロギングするのと打って変わって、いろいろな下準備が必要なのと、ログを記録するごとにログエントリーオブジェクトを作る必要があるのが分かります。ちょっとめんどくさそうですね。

index.js
const{Logging}=require('@google-cloud/logging')exports.loggingWithClient=async(req,res)=>{// クライアントを作るconstlogging=newLogging()// ログ出力先を決めてロガーを作るconstlog=logging.log('my-name')// ログエントリーを作るconstentry=log.entry({resource:{type:'global'},severity:'INFO',// 重要度「INFO」を指定},'ログをクライアントで書き込むテスト',)// ログを書き込むawaitlog.write(entry)res.send('OK')}

ひとまずこれをデプロイして、

gcloud functions deploy loggingWithClient --runtime=nodejs12 --trigger-http

呼び出してみます:

curl https://asia-northeast1-${PROJECT}.cloudfunctions.net/loggingWithClient

どのようにログが記録されたか、ログビューアを開いてみます。console.logで記録したログは、自動的にどの関数のものか関連付けされるため、管理コンソールの「Cloud Functions」から当該関数の「ログを表示」から行く導線が使えましたが、上のコードで記録したログは関数に紐づけてロギングしていないので、「ロギング」の「ログビューア」から探しに行きます:

この導線からだと、プロジェクトの全ログが出るので、たくさんログがある場合は「直近の3分」などで絞り込むと見つけやすいです。

このように、console.infoなどではできなかった重要度「INFO」でロギングされているのが確認できます:

今回試したサンプルコードでは、ログエントリーのメタデータを色々省いたため、かなり質素な内容になっています:

下は普通にconsole.logしただけのログエントリーですが、それと比べると情報の少なさが分かります:

まとめ

この投稿を通じて、次のことが分かったと思います。

  • Google Cloud Function & Node.jsで@google-cloud/loggingを使って重要度をつけたログを記録する方法
  • そして、その面倒くささ。
  • console.logと@google-cloud/loggingで記録されるログ内容の違い。

@google-cloud/loggingはかなり低レベルなロギングができる一方、使い勝手が良くなく、どの関数で実行されたかなどは自動的に記録されないので、次回はもっと利便性の高い方法について投稿したいと思います。

aws-amplify? 突然 TypeError: Cannot read property 'configure' of undefined が出て何も表示されなくなった

$
0
0

前提条件

React + Next.js + TypeScript 環境で next start

事象

以下のコンソールエラーが出て表示は真っ白になる。ちなみに開発環境(nextコマンド実行時)では問題なし。

TypeError:Cannotreadproperty'configure'ofundefined

configureというメソッドはうちのプロダクトだと Amplify.configureしか心当たりがなかったため、そのあたりを調査。

対応

yarn add @aws-amplify/core #or npm install @aws-amplify/core
pages/_app.tsx
- import Amplify from 'aws-amplify';
+ import Amplify from '@aws-amplify/core';

これで治った。
公式ドキュメントでもどちらでも良いと記載してある。
https://github.com/aws-amplify/amplify-js#configuration

あとがき

特に aws系のパッケージのアップデートをしたわけでもなく突然発生。
それも開発環境では発生せずステージング環境だけで発生したためやや焦った。
ビルド時に変数名とかメソッド名が圧縮されてエラーになってるのかなと推測したが原因まではつかめず。
aws-amplifyだけの原因でなく、他のパッケージや環境との兼ね合いがあるかも。

ググってもあまり情報が出なかったため記しておく。
もし同様のエラーでお悩みの方はお試しあれ。


AngularでBootstrap5を使う準備

$
0
0

はじめに

先日、Bootstrap 5 alphaがリリースされました。
jQueryとの依存関係が削除されたりして、シンプルにAngularと一緒に使えると考えました。
これは、そのメモです。

Angularプロジェクトの作成

まずは、テスト用に新しいプロジェクトを作ります。
任意のディレクトリに「angular-bootstrap-test」というプロジェクトを作成します。

$ng new angular-bootstrap-test
$? Would you like to add Angular routing? (y/N) y
$Which stylesheet format would you like to use? (Use arrow keys)    CSS
>Sass
    Less
    Stylus

Bootstrap5のインストール

作成したプロジェクトのデイレクトリに移動して、npmでインストールする。

$cd angular-bootstrap-test
$npm install bootstrap@next

これで、Bootstrap5がインストールされ、angular-bootstrap-test/node_modulesにbootstrapディレクトリができる。

使うための準備

Bootstrap5を使うために、angular.jsonを編集する。

"style":[
    "src/styles.css" ,
    "node_modules/bootstrap/dist/css/bootstrap.css"  //追加
],
"scripts":[
    "node_modules/bootstrap/dist/js/bootstrap.js"  //追加
],

これで使用できるようになりました。

使ってみる

app.component.htmlに適当に適当に追記してみる。

<p class="bg-dark text-light">Bootstrap test</p>

参考

https://v5.getbootstrap.com/docs/5.0/getting-started/download/

Google Functions & Node.js: winstonでロギングする

$
0
0

本稿では、Google Could Platform(GCP)のGoogle Cloud Functions(GCF)で、Node.jsのロギングライブラリwinstonを使ったログの記録方法を説明します。

この投稿で学べること

  1. winstonをGoogle Cloud Functionsの関数に組み込む方法
  2. winstonのメソッドの呼び出し方
  3. winstonでオブジェクトの値をロギングする方法
  4. winstonで記録したログの読み方

Cloud Functionsにてwinstonでロギングする方法

winstonはロギングを抽象化したライブラリで、GCPに限ったものではなく、Node.jsのアプリケーションのロギングを手助けするものです。ログの出力先を、コンソールやファイルなど指定できたり、ログのレベル(INFO, WARN, ERROR)などを指定しながらロギングできるAPIが生えていて便利です。

GCFではconsole.logでもロギングできますが、低レベルなロギングしかできません。winstonを使うことでより、高レベルのロギングができます。

:bulb:console.logで行う低レベルなロギングについて知りたい方はこちら↓
Google Functions & Node.js: console.logを使った最低限のロギング - Qiita

winstonをインストールする

まずロギングライブラリのwinstonをインストールします:

yarn add winston

winstonを関数に組み込む

次にwinstonを関数に組み込みます。

winstonの設定コード

"winston"モジュールからwinstonオブジェクトを取り出し、createLoggerメソッドでロガーを作ります。

index.js
constwinston=require('winston')constlogger=winston.createLogger({level:'silly',// 記録するログレベルの設定transports:[newwinston.transports.Console(),// どこにログ出しするかの設定],})

createLoggerメソッドにはロガーの設定を渡します。levelは記録するログレベルで、次の7段階があります:

レベル優先度(小さいほうが重要)
error0
warn1
info2
http3
verbose4
debug5
silly6

levelに指定したログレベル以下のログが記録されるようになります。例えば、infoを設定すると、errorからinfoまでが記録され、http以降のログは記録されません。上のサンプルコードではsillyを設定しているので、全レベルが記録されます。

transportsの設定は、どこにログを出すかの設定です。winston.transports.Consoleconsole.logなどと同等と考えてください。Console以外にもファイルに出したりもできますが、Google Cloud Functionsではファイルにログ出しするという運用は通常行いません。

ロギングするコード

winstonの設定コードを書いたら、今度は関数実行時にロギングするコードを書きます。先程作成したloggerオブジェクトのメソッドを呼び出すことで、ロギングができます。

index.js
exports.loggingWithWinston=async(req,res)=>{logger.error('errorのメッセージ')logger.warn('warnのメッセージ')logger.info('infoのメッセージ')logger.verbose('verboseのメッセージ')logger.debug('debugのメッセージ')logger.silly('sillyのメッセージ')}

関数の全体像

以上のwinstonの設定コードとロギングするコードを組み合わせてると関数が完成します。次が完成形のコードです:

index.js
constwinston=require('winston')constlogger=winston.createLogger({level:'silly',// 記録するログレベルの設定transports:[newwinston.transports.Console(),// どこにログ出しするかの設定],})exports.loggingWithWinston=async(req,res)=>{logger.error('errorのメッセージ')logger.warn('warnのメッセージ')logger.info('infoのメッセージ')logger.verbose('verboseのメッセージ')logger.debug('debugのメッセージ')logger.silly('sillyのメッセージ')res.send('OK')}

関数をデプロイして試す

実装ができたので、関数をデプロイします:

gcloud functions deploy loggingWithWinston --runtime=nodejs12 --trigger-http

デプロイが完了したら、関数を呼び出してみます:

curl https://asia-northeast1-${PROJECT}.cloudfunctions.net/loggingWithWinston

関数のログビューアを開き、ちゃんとログが出ているか見てみます:

CleanShot 2020-08-06 at 09.37.28@2x.png

ちゃんとログがでているのが分かります。

winstonのログレベルの情報はどこ?

今回記録したログはどれも「重大度」はDEFAULTになっています。これは、winston.transports.Consoleが内部でconsole.logなどを使っているためです。

:bulb:console.logとGCPの「重大度」の関係性についての詳細はGoogle Functions: console.infoやconsole.errorなどとログビューアの「重大度」の関係性をご覧ください。

では、どこにwinstonのログレベルが残っているのでしょうか? ログエントリの詳細を開くと分かります。

CleanShot 2020-08-06 at 09.41.58@2x.png

このログはlogger.info()で記録したものですが、jsonPayload.level"info"とあるのが見て取れます。これが、winstonのログレベルです。

ちなみに、ログビューアではjsonPayload.levelの値でフィルタリングすることができます。値をクリックするとメニューが出てくるので、その中から「一致エントリを表示」をクリックします:

CleanShot 2020-08-06 at 09.44.55@2x.png

すると、クエリが更新され、値に一致するログエントリのみに絞り込まれます:

CleanShot 2020-08-06 at 09.48.30@2x.png

オブジェクトの値をログに出す

winstonのロギングメソッドは、第二引数にメタ情報を渡すことができます。メタ情報とは、ログメッセージに関連するデータのことです。ログメッセージがどういう状況で出たのかを後から分かるように、それに関連するデータを一緒に記録できるわけです。

下の例では、ログメッセージと一緒に記録したいオブジェクトをロギングするものです:

index.js
constwinston=require('winston')constlogger=winston.createLogger({level:'silly',// 記録するログレベルの設定transports:[newwinston.transports.Console(),// どこにログ出しするかの設定],})exports.loggingWithWinston=async(req,res)=>{// 一緒に記録したいデータconstobject={boolean:true,number:1,string:'string',array:[true,1,'string'],object:{a:true,b:1,c:'string'},set:newSet([true,1,'string']),date:newDate(),}logger.error('errorのメッセージ+object',object)logger.warn('warnのメッセージ+object',object)logger.info('infoのメッセージ+object',object)logger.verbose('verboseのメッセージ+object',object)logger.debug('debugのメッセージ+object',object)logger.silly('sillyのメッセージ+object',object)res.send('OK')}

この関数をデプロイして、呼び出してみるとログビューア上でオブジェクトの値を確認できます:

CleanShot 2020-08-06 at 09.59.53@2x.png

メタ情報はjsonPayloadのプロパティ組み込まれます。これにより運用時にログの状況を確認したり、ログのフィルタリングや分析に活用することができます。

注意点として、記録できるメタ情報はJSON.stringifyできる値のみという点です。上の例ではSetオブジェクトをメタ情報に含めていますが、SetはJSON化すると{}になるのでログからは情報が欠けてしまっています。また、DateオブジェクトはJSON化すると文字列になったりしています。

Google Functions & Node.js: bunyanで高レベルなロギングをする方法

$
0
0

本稿では、Google Could Platform(GCP)のGoogle Cloud Functions(GCF)で、Node.jsのロギングライブラリbunyanを使ったログの記録方法を説明します。

この投稿で学べること

  1. bunyanをGoogle Cloud Functionsの関数に組み込む方法
  2. bunyanのメソッドの呼び出し方
  3. bunyanでオブジェクトの値をロギングする方法
  4. bunyanで記録したログの読み方

この投稿では説明しないこと

  • bunyanを使ってStackDriverにロギングする方法
  • bunyanを使ってStackDriverに「重要度」をつけながらロギングする方法

bunyanとStackDriverを組み合わせると、より自由度が高く分析しやすい構造でロギングすることが可能なりますが、この投稿ではそれについては説明しません。bunyanを単純にCloud Functionsで使うだけでも高レベルなロギングができて便利です。本稿はそこにフォーカスして説明することにします。

Cloud Functionsにてbunyanでロギングする方法

bunyanはロギングを抽象化したライブラリで、GCPに限ったものではなく、Node.jsのアプリケーションのロギングを手助けするものです。ログの出力先を、コンソールやファイルなど指定できたり、ログのレベル(INFO, WARN, ERROR)などを指定しながらロギングできるAPIが生えていて便利です。

GCFではconsole.logでもロギングできますが、低レベルなロギングしかできません。bunyanを使うことでより、高レベルのロギングができます。

:bulb:console.logで行う低レベルなロギングについて知りたい方はこちら↓
Google Functions & Node.js: console.logを使った最低限のロギング - Qiita

bunyanをインストールする

まずロギングライブラリのbunyanをインストールします:

yarn add bunyan

bunyanを関数に組み込む

次にbunyanを関数に組み込みます。

bunyanの設定コード

"bunyan"モジュールからbunyanオブジェクトを取り出し、createLoggerメソッドでロガーを作ります。

index.js
constbunyan=require('bunyan')constlogger=bunyan.createLogger({name:'my-function'})

createLoggerメソッドにはロガーの設定を渡します。いろいろな設定が可能ですが、必須設定のnameフィールドだけ指定しておきます。

ロギングするコード

bunyanの設定コードを書いたら、今度は関数実行時にロギングするコードを書きます。先程作成したloggerオブジェクトのメソッドを呼び出すことで、ロギングができます。

index.js
exports.loggingWithBunyan=async(req,res)=>{logger.fatal('fatalのメッセージ')logger.error('errorのメッセージ')logger.warn('warnのメッセージ')logger.info('infoのメッセージ')logger.debug('debugのメッセージ')logger.trace('traceのメッセージ')}

関数の全体像

以上のbunyanの設定コードとロギングするコードを組み合わせてると関数が完成します。次が完成形のコードです:

index.js
constbunyan=require('bunyan')constlogger=bunyan.createLogger({name:'my-function'})exports.loggingWithBunyan=async(req,res)=>{logger.fatal('fatalのメッセージ')logger.error('errorのメッセージ')logger.warn('warnのメッセージ')logger.info('infoのメッセージ')logger.debug('debugのメッセージ')logger.trace('traceのメッセージ')res.send('OK')}

関数をデプロイして試す

実装ができたので、関数をデプロイします:

gcloud functions deploy loggingWithBunyan --runtime=nodejs12 --trigger-http

デプロイが完了したら、関数を呼び出してみます:

curl https://asia-northeast1-${PROJECT}.cloudfunctions.net/loggingWithBunyan

関数のログビューアを開き、ちゃんとログが出ているか見てみます:

CleanShot 2020-08-06 at 10.35.05@2x.png

JSONらしきものが記録されていることが分かります。ログエントリを開いて詳細を見てみます:

これは、logger.fatal()で記録したログエントリです。これを見ると、メッセージがしっかり記録されていることが分かります。

bunyanのログレベルの情報はどこ?

今回記録したログはログビューア上の「重大度」がどれもDEFAULTになっています。これは、bunyanが内部でconsole.logなどを使っているためです。

:bulb:console.logとGCPの「重大度」の関係性についての詳細はGoogle Functions: console.infoやconsole.errorなどとログビューアの「重大度」の関係性をご覧ください。

では、どこにbunyanのログレベルが残っているのでしょうか? 先程のログエントリ詳細を見返してみましょう。

このログはlogger.fatal()で記録したものですが、jsonPayload.level60とあるのが見て取れます。これが、bunyanのログレベルfatalを指す値です。

ちなみに、ログビューアではjsonPayload.levelの値でフィルタリングすることができます。値をクリックするとメニューが出てくるので、その中から「一致エントリを表示」をクリックします:

CleanShot 2020-08-06 at 10.43.55@2x.png

すると、クエリが更新され、値に一致するログエントリのみに絞り込まれます:

CleanShot 2020-08-06 at 10.47.19@2x.png

オブジェクトの値をログに出す

bunyanのロギングメソッドに渡せるのは、文字列に限ったものではありません。オブジェクトを渡すこともできます。

下の例では、ログメッセージと一緒に記録したいオブジェクトをロギングするものです:

index.js
constbunyan=require('bunyan')constlogger=bunyan.createLogger({name:'my-function'})exports.loggingWithBunyan=async(req,res)=>{constobject={boolean:true,number:1,string:'string',array:[true,1,'string'],object:{a:true,b:1,c:'string'},set:newSet([true,1,'string']),date:newDate(),}logger.fatal({message:'fatalのメッセージ+object',object})logger.error({message:'errorのメッセージ+object',object})logger.warn({message:'warnのメッセージ+object',object})logger.info({message:'infoのメッセージ+object',object})logger.debug({message:'debugのメッセージ+object',object})logger.trace({message:'traceのメッセージ+object',object})res.send('OK')}

この関数をデプロイして、呼び出してみるとログビューア上でオブジェクトの値を確認できます:

20200806105656@2x.png

オブジェクトはjsonPayloadのプロパティで参照できます。これにより運用時にログの状況を確認したり、ログのフィルタリングや分析に活用することができます。

注意点として、記録できるオブジェクトはJSON.stringifyできる値のみという点です。上の例ではSetオブジェクトをオブジェクトに含めていますが、SetはJSON化すると{}になるのでログからは情報が欠けてしまっています。また、DateオブジェクトはJSON化すると文字列になったりしています。

【node.js】 node.jsインストール 芋っていたけど、簡単だった件...

$
0
0

【ゴール】

node.jsを自身のPCインストール

【目次】

■node.jsとは

■インストール

■確認

■実際にファイルを作成,node起動

【環境】

■ Mac OS catalina
■ node v12.18.3
■ npm v6.14.6

【開始】

node.jsとは

■サーバーサイドで動くjavascriptのライブラリ
■動きが早い
■チャットや同時に多数の接続があっても耐える
■npmも同梱とお得。

インストール

下記の公式ページへ。
https://nodejs.org/ja/download/

*OSによって異なるので注意。
*確認事項ありますが、全て確認し、進めてください。

確認

実際にPCにインストールされたか確認

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

mac.terminal
$ node -v
v12.18.3

■npmのバージョンを確認
*npmとは「node package manager」の略。nodeのマネージャーさんですね。

mac.terminal
$ npm -v
6.14.6

実際にファイルを作成,node起動

■Descktopに適当にディレクトリ作成
■その中にファイル「index.html」「index.js」を用意

mac.terminal
$ cd Descktop
$ mkdir JS
$ cd JS 
$ touch index.html
$ touch index.js

■node.jsのローカル環境を起動

mac.terminal
$ npx @js-primer/local-server

■html/jsファイルに追記

index.html
<p>hello.</p><script src="index.js"></script>![スクリーンショット 2020-08-06 12.10.24.png]
index.js
conole.log('hello,world');

すると....

スクリーンショット 2020-08-06 12.12.20.png

以上。
あとはフレームワーク等使用すればいいですね。

【合わせて読みたい】

■ 【comandLine】 一言で コマンドライン 各種コマンド ターミナル
https://qiita.com/tanaka-yu3/items/b32e353bd6d7c9ebd4fb

■ 【javascript】 テンプレートリテラル とは 一言で。
https://qiita.com/tanaka-yu3/items/9b07bd9fc4126291be28

■アプリケーション開発の準備 · JavaScript Primer #jsprimer
https://jsprimer.net/use-case/setup-local-env/

AWS lightsailでディレクトリルートを変更する

$
0
0

AWS lightsail + node.jsでルートディレクトリを変更したい

node.jsをインストールしたlinuxベースのAWS lightsailで、
gitクローンしたディレクトリをディレクトリルートに指定します。

laightsailの環境は
・nodejs v12.16.1
・apache v2.4.41(unix)

sudo vi /opt/bitnami/apache2/conf/bitnami/bitnami.conf

で、viエディタを開きます。

escキーを押してから

/DocumentRoot

でドキュメントルートを指定している行にジャンプします。
(普通にカーソル移動でもokです。)

ここで、

DocumentRoot "gitクローンしたファイルパス"

IPアドレスやドメインをたたけば、htmlファイルが表示されます。

Viewing all 8909 articles
Browse latest View live