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

Mac環境にNode.jsをインストールする

$
0
0

はじめに

Mac環境にNode.jsをインストールする方法としていくつかの方法があります。ここではバージョン管理を行うためにnodenvを利用した方法についてまとめます。

インストール

Node.jsをインストールするためには、以下をインストールする必要があります。

  1. Homebrewをインストール
  2. nodenvをインストール

Homebrewのインストール

Homebrewをインストールします。Homebrewに書かれているインストールコマンドを実行します。

$ /usr/bin/ruby -e"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

brew doctorを実行して、インストール確認を行います。

$ brew doctor
Your system is ready to brew.

これでHomebrewのインストールは完了です。

nodenvのインストール

Homebrewからnodenvをインストールします。

$ brew install nodenv

~/.bash_profile、または~/.zprofileにeval "$(nodenv init -)"を追加します。
nodenv initを実行することで、どこに何を追加したらよいのかを確認することができます。

$ nodenv init
# Load nodenv automatically by appending# the following to ~/.bash_profile:eval"$(nodenv init -)"

nodenv-doctorを実行してインストール確認します。

$ curl -fsSL https://github.com/nodenv/nodenv-installer/raw/master/bin/nodenv-doctor | bash
Checking for`nodenv' in PATH: /usr/local/bin/nodenv
Checking for nodenv shims in PATH: OK
Checking `nodenv install' support: /usr/local/bin/nodenv-install (node-build 4.9.32)
Counting installed Node versions: none
  There aren't any Node versions installed under `/Users/*****/.nodenv/versions'.
  You can install Node versions like so: nodenv install 2.2.4
Auditing installed plugins: OK

これでnodenvのインストールは完了です。

Node.jsのインストール

nodenvからNode.jsをインストールします。
インストール可能なバージョンを確認します。

$ nodenv install-list
0.1.14
0.1.15
0.1.16

# 省略...

nightly
node-dev
rc
v8-canary

バージョンを指定してNode.jsをインストールします。(ここでは14.16.0をインストール)

$ nodenv install 14.16.0
Downloading node-v14.16.0-darwin-x64.tar.gz...
-> https://nodejs.org/dist/v14.16.0/node-v14.16.0-darwin-x64.tar.gz
Installing node-v14.16.0-darwin-x64...
Installed node-v14.16.0-darwin-x64 to /Users/*****/.nodenv/versions/14.16.0

インストールした後は、nodenv rehashを実行しておきます。

$ nodenv rehash

これでNode.jsのインストールは完了です。

使用するNode.jsの指定

nodenv globalを実行して使用するNode.jsのバージョンを指定します。(ここでは14.16.0を指定)

$ nodenv global 14.16.0 

また、プロジェクト別(ディレクトリ別)に指定することもできます。

$ nodenv local 14.16.0 

アンインストール

Node.jsのアンインストール

特定のバージョンをアンインストールしたい場合はnodenv uninstallを実行します。(ここでは14.16.0をアンインストール)

$ nodenv uninstall 14.16.0

【Node.js】asyncの使い方

$
0
0

プログラミング勉強日記

2021年3月12日
JavaScriptの非同期処理についてはこちらの記事で詳しく触れてる。

asyncの基本的な書き方

asyncの導入
$ npm install async
// require()を使って使えるようにするvarasync=require('async');

 メソッドの第1引数に配列、オブジェクトで複数の関数処理を記述することで非同期処理が実行できる。

基本的な書き方
async.メソッド名([関数,関数,関数,...],関数);

seriesで非同期処理を行う

async.series([function(callback){callback(null,"1");},function(callback){callback(null,"2");},function(callback){callback(null,"3");}],function(err,result){console.log(result);});
実行結果
[1, 2, 3]

参考文献

【JavaScript】 async / await の使い方
【Node.js入門】asyncの使い方と非同期処理の制御方法まとめ!

Expressのnext()は関数を抜けない。次の処理を呼び出すだけ

$
0
0

Expressのミドルウェアのnext()について色々勘違いしていたせいで若干戸惑ったので、顛末を残しておきます。

前提

Node.js 14.15.3
Express 4.17.1

起こったこと

Expressでリクエストから渡されたトークンを処理するミドルウェアを実装していました。

//get jwt token from authorization header//set token to token field of requestconsttokenExtractor=(request,response,next)=>{constauthorization=request.get('authorization')if(authorization&&authorization.toLowerCase().startsWith('bearer ')){request.token=authorization.substr(7)next()}request.token=nullnext()}

後続の処理でrequest.tokenみたいにトークンを扱えるようにするミドルウェアです。
後には以下のような処理が続きます。

/**
 * store new blog if a valid token is sent with the request
 */blogsRouter.post('/',async(request,response)=>{constbody=request.bodyconsttoken=request.tokenconstdecodedToken=jwt.verify(token,process.env.SECRET)if(!token||!decodedToken.id){returnresponse.status(404).json({error:'token missing or invalid'})}constuser=awaitUser.findById(decodedToken.id)constblog=newBlog({title:body.title,author:body.author,url:body.url,likes:Number(body.likes),user:user._id})constsavedBlog=awaitblog.save()console.log(savedBlog)//save user-blog relationuser.blogs=user.blogs.concat(savedBlog)awaituser.save()returnresponse.json(savedBlog)})

トークンがマッチしてたらBlogを新規登録できるという処理です。
で、ここのjwt.verify()の呼び出しで、トークンがある場合でも第一引数にnullが渡されていることが発覚しました。
つまりrequest.tokenにnullがセットされているということを意味します。
request.tokenがnullということは、ミドルウェア内の

if(authorization&&authorization.toLowerCase().startsWith('bearer ')){request.token=authorization.substr(7)next()}request.token=nullnext()

ここが怪しそうです。
でもAuthorizationヘッダーにトークンがある場合はifの条件に引っかかって、if内のnext()で次の処理に移るのでは?
しかしいくら確認してもrequest.tokenはnullのままです。

公式ドキュメントを見てみる

上記の next() の呼び出しに注意してください。この関数を呼び出すと、アプリケーション内の次のミドルウェア関数が呼び出されます。

next()は次の処理を呼び出すコールバックで、現在の処理を抜けるわけではないっぽいです。
next()を呼ぶと、現在の関数を抜けて次の処理に移るのかなと勘違いしてしまっていたので、妙な動きをしてしまっていたわけです。

以下のようにelseを追加して修正すると、しっかりトークンの値を取得することができました。

//get jwt token from authorization header//set token to token field of requestconsttokenExtractor=(request,response,next)=>{constauthorization=request.get('authorization')if(authorization&&authorization.toLowerCase().startsWith('bearer ')){request.token=authorization.substr(7)next()}else{request.token=nullnext()}}

余談

解決済みですが原因がわからないものなので、余談とします。
今回の件ではトークンの値が取れなかったこと以外に、もう一つエラーが発生していました。以下のようなものです。

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:558:11)
    at ServerResponse.header (/Users/xxx/projects/bloglist/node_modules/express/lib/response.js:771:10)
    at ServerResponse.send (/Users/xxx/projects/bloglist/node_modules/express/lib/response.js:170:12)
    at ServerResponse.json (/Users/xxx/projects/bloglist/node_modules/express/lib/response.js:267:15)
    at /Users/xxx/projects/bloglist/controllers/blogs.js:47:18
    at processTicksAndRejections (internal/process/task_queues.js:93:5)

レスポンス返した後にレスポンスヘッダーに値をセットしようとして発生したエラーっぽいです。つまりレスポンスを返してからさらに何かしらの処理を行おうとしたというわけです。
で、ミドルウェアの話に戻るのですが、このエラーはミドルウェア修正後は発生しなくなりました。なのでなんとなく関係ありそうな気がします。
修正前のミドルウェアはこういうものでした。

//get jwt token from authorization header//set token to token field of requestconsttokenExtractor=(request,response,next)=>{constauthorization=request.get('authorization')if(authorization&&authorization.toLowerCase().startsWith('bearer ')){request.token=authorization.substr(7)next()}request.token=nullnext()}

if内でまず1つ目のnext()が呼ばれます。後続の処理は上にあるような「トークンが正しいものならBlogを新規作成する」というものですが、この処理の中ですでにレスポンスを返しています。
で、その後ifを抜けて次のnext()が呼ばれます。
ここでレスポンスオブジェクトに対して最終的にヘッダーに値を付与するような処理が走るのだと思いますが...ちょっとまだ理解が浅いせいでどういう流れでそうなるのかが追えませんでした。
もしどなたか詳しくて優しい方がいらっしゃれば、コメントで教えていただけるとありがたいです。

[Node.js] 非同期処理 - async/await編

$
0
0

async/await

async/await構文

functionparseJSONAsync(json){returnnewPromise((resolve,reject)=>{setTimeout(()=>{try{resolve(JSON.parse(json))}catch(err){reject(err)}},1000)})}asyncfunctionasyncFunc(json){try{constresult=awaitparseJSONAsync(json);console.log('パース結果',result);}catch(err){console.log('エラーをキャッチ',err);}}asyncFunc('{"foo": 1}')asyncFunc('不正なJSON')>>>ース結果{foo:1}エラーをキャッチSyntaxError:UnexpectedtokeninJSONatposition0atJSON.parse(<anonymous>)...(省略)

asyncキーワードの付いた関数は必ずPromiseインスタンスを返します。それは関数内で非同期処理が実行されない場合でも、同様です。

asyncfunctionasyncReturnFoo(){return'foo'}constresult=asyncReturnFoo()console.log(result)>>>Promise{'foo'}

また、async関数内でエラーが投げられると、async関数はrejectedなPromiseインスタンスを返します。

asyncfunctionasyncTrhow(){thrownewError('エラー')}constresult=asyncTrhow()console.log(result)>>>Promise{<rejected>Error:エラー...(省略)}(node:23161)UnhandledPromiseRejectionWarning:Error:エラー...(省略)

awaitはasync関数ないの処理を一時停止するものの、非同期I/Oのようにスレッドの処理をブロックするわけではありません。asunc関数が一時停止している間も、async関数の外の処理は動作を続けます。

asyncfunctionpauseAndResume(pausePeriod){console.log('pauseAndResume開始')awaitnewPromise(resolve=>setTimeout(resolve,pausePeriod))console.log('pauseAndResume終了')}pauseAndResume(1000)console.log('async関数外の処理はawaitの影響を受けない')>>>pauseAndResume開始async関数外の処理はawaitの影響を受けないpauseAndResume終了

並行処理

async/await構文には並行実行のための機能はないため、Promise.all()などのスタティックメソッドと組み合わせる必要がある。

asyncfunctiondoSomethingAsyncConcurrently(){constresult=awaitPromise.all([asyncFunc1(),asyncFunc2(),asyncFunc3()]);}

for await ...of 構文

for await ... of はasyncイテラブルに対して反復処理を行います。
asyncジェネレータ関数を使って簡単な例を書いてみます。

syncfunction*asyncGenerator(){leti=0;while(i<=3){awaitnewPromise(resolve=>setTimeout(resolve,1000));yieldi++}}forawait(constvalueofasyncGenerator()){console.log(value);}>>>0123

nvmで固定のNode.jsのバージョンを使わせる方法

$
0
0

nvmのインストール方法からnvmの使い方について

以下が参考になる
* https://qiita.com/ffggss/items/94f1c4c5d311db2ec71a

プロジェクトで固定のNode.jsのバージョンを使わせる方法

versionを"v12.21.0"で固定する場合

$ echo "lts/*" > .nvmrc

最新のltsで固定する場合

$ echo "lts/*" > .nvmrc

nvm installの実行(初回のみ)

$ nvm install
Found '/path/to/project/.nvmrc' with version <v12.21.0>
Now using node v12.21.0 (npm v6.14.11)

nvm useの実行(初回のみ)

$ nvm use
Found '/path/to/project/.nvmrc' with version <v12.21.0>
Now using node v12.21.0 (npm v6.14.11)

参考

Prettierについて

$
0
0

Prettierについて

Prettierの利用方法

package.jsonの生成

npm init -y

Prettier のインストール

npm install prettier@2.1.1 -D

-Dは--save-devの略で、ローカルインストールを意味する。

パスを通す

export PATH=$PATH:./node_modules/.bin

Prettierの実行

prettier ファイル名 --write

ESLintとの併用

  • Prettierはあくまでコードフォーマッター
  • ESLintのような構文チェック機能はない
  • Prettierでコードフォーマット、ESLintで構文チェックすることで、双方の利点を活用する
  • eslint --fixだけでコードの整形と構文チェックを可能とする

併用に必要なパッケージのインストール

npm install eslint eslint-config-prettier eslint-plugin-prettier -D
  • eslint(ESLint 本体)
  • eslint-config-prettier → ESLint のフォーマット関連のルールを全て無効化し、Prettierに任せる)
  • eslint-plugin-prettier → PrettierをESLint上で実行可能とする

.eslintrcの設定

{"env": {"browser": true,
    "es6": true},
  "extends": ["eslint:recommended", // esLintの推奨設定をチェック
    "plugin:prettier/recommended" // prettierの推奨設定をチェック
  ],
  "rules": {   // prettierの場合のエラールール設定(.prettierrcに別ファイルとして記載しても良い?)"prettier/prettier": ["error",
      {"singleQuote": true,
        "trailingComma": "es5"}]}}

eslintの実行

eslint --fixファイル名 

git commit時にeslint --fixが実行されるようにする

パッケージのインストール

npm install lint-staged husky -D

package.jsonに以下を追加

{"husky":{"hooks":{"pre-commit":"lint-staged"}},"lint-staged":{"*.js":"eslint --fix"}}

参考

SAM+Node.jsでLambdaをInvokeした際に「Error: Cannot find module 'luxon'」が出た時の対処法

$
0
0

問題点

以下でluxonをインストール

npm install luxon --save-dev

package.jsonに以下が追加される

package.json
"dependencies":{"luxon":"^1.26.0"}

SAM+Node.jsでLambdaをInvokeした際に以下エラー発生

sam local invoke TranslationFunction --event request.json
Invoking index.handler (nodejs12.x)
Skip pulling image and use local one: amazon/aws-sam-cli-emulation-image-nodejs12.x:rapid-1.20.0.

(中略)

2021-03-12T22:47:33.403Z    undefined   ERROR   Uncaught Exception  {"errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot find module 'luxon'\nRequire

(中略)

原因

以下の実行に問題あり

npm install luxon --save-dev

解決策

まずはアンインストール

npm uninstall luxon --save-dev

再度インストール(オプションを変更する)

npm uninstall luxon --save

package.jsonを確認

以下が追加されていればOK

package.json
"dependencies":{"luxon":"^1.26.0"}

参考

degit - ! could not find commit hash for master

$
0
0

結論

じつに単純なことだった。公式ドキュメントにもしっかり書いてある

bash
$ npx degit sprout2000/react-typescript#main react-typescript

経緯

degitは、Github 上のレポジトリを .gitディレクトリを除いた状態でダウンロードすることができるツールです。

しかし、ある日突然 degitでレポジトリのダウンロードができなくなりました。

bash
% npx degit sprout2000/react-typescript react-typescript   
! could not find commit hash for master

うえのエラーメッセージでググると

あたりがヒットするので、斜め読みしながら Xcode Command Line Tools の再インストールなどを試してみたものの、いっこうに改善せず・・・

で、エラーメッセージをもう一度眺めると for master !!!

そうです。昨年(2020)より新規の GitHub レポジトリのデフォルトブランチ名が masterから mainに変わっただけだったのです。

degitでは #ブランチ名でブランチを指定してダウンロードできるので、

bash
$ npx degit foobar#main foobar

とすれば良かっただけの話でした。

教訓

エラーメッセージをちゃんと読めば、うまくいかない理由が(たいてい)書いてある。


【Node.js】ORMでの大量レコードSELECTによるヒープアウトからメモリを救う話

$
0
0

時と場合により、一度のAPIリクエストで数万行を超えるレコードを取得して、それを加工してレスポンスしたいってこともあると思います。

Nodeだと、オプション指定せずに起動させるとデフォルトのメモリ割り当てが512MB(700MBとか1400MBのケースもある?)なので、意外と簡単にヒープアウトしてしまいます。

例として、自分の環境ではTypeORMで25万行を超えるレコードを読み込み、それを加工しようとするとヒープアウト => APIサーバが死亡しました。

1回のAPIコールでサーバの死亡を引き起こす可能性があるのは当然許容できないので、対策を考えていきます。

対策

できることは全てやろうということで、考えられる対策を思いつく限り挙げていきます。

V8エンジンへのメモリ割り当てを増やす

最も安直かつ簡単に取れる対策です。根本的な解決ではないため、これだけやって終わりにするのはエンジニアとしても終わりです。

安直とは書きましたが、少なくともデフォルトの512MBは本番稼働するAPIサーバとしては心もとないため、増やしておくのが無難でしょう。

割当量はNode起動コマンドの実行時に、--max-old-space-sizeオプションで指定できます。

node --max-old-space-size=2048 dist/app.js

上記では、メモリ割当を2GBに指定しています。

一度に取得するデータ量を減らす

この検討も必須です。
そもそもそんなに大量のデータを一度に取得する必要ってあるんだっけ?って話ですね。

もちろん、「何かしらのデータを1年分、CSVとしてダウンロードする」のような要件であれば、この程度のデータ量になることは往々にしてあると思います。

ただ、例えば「あるページに表示するためのデータを取得する」要件である場合、そんなに大量のデータを頑張って取得してフロントエンドに表示させたところで、ユーザビリティは最悪ですし、ユーザーのブラウザ環境によりレンダリング速度に大きな差がでるなど、また別の問題に直面するであろうことは明らかです。

この場合、そもそもページネーションを実装してないのがおかしい => ページネーションさえ実装すればAPI側のヒープアウトも自然解消する、といったケースもあるでしょう。

DBから取得するデータ量を必要最低限に絞る

取得カラムを減らす

カラムが減ればメモリに読み込むデータも減ります。
当然、取得データ量が減ればネットワークIOの時間も減ります。
さらに、SELECT対象のカラムが減ることでSQL実行時間が減る場合もあります。

特にORMを使っていると、安易に対象テーブルの全カラムを取得するコードを書きがち(少なくとも僕はそうです)なのですが、JOINするテーブルが増えていくと、これが無視できないレベルになってきます。

// 書きがちなダメダメクエリビルダーimport{getManager}from"typeorm"asyncfunctiongetOrdersByIds(orderIds:number[]){returngetManager().createQueryBuilder().select("o")// <- ordersテーブルの全カラムを取得している.from(Order,"o").innerJoinAndSelect("o.items","oi")// <- .innerJoin"AndSelect"を使っているため、order_itemsの全カラムがSELECT句に反映されてしまう.where("o.id IN (:orderIds)",{orderIds}).getMany()}

本当に全カラムが必要な場合は仕方ないとして、多少面倒でも、SELECTカラムは必要最低限にしておくのがベストでしょう。

不要なJOINをしてないか再度確かめる

実際コード内では使ってない不要なテーブルをJOINしちゃってる場合など、それを削除するだけでレコード量が10分の1とかになることはままあります。

これ一発でボトルネックが解消されることも無くはないので、ちゃんとチェックしましょう。

ORマッパーを利用する際の自動変換に注意する

ORMの本懐ですが、取得したデータを自動的にデータ構造を保ったオブジェクトとして返却してくれるのって便利ですよね。

ただ、当たり前ですが、この処理の裏側ではDBからのローデータを頑張ってオブジェクトに変換する処理がORMにより行われているわけです。
つまり、データが大きくなるにつれて、ORMのユーザー(つまり僕たち)がハンドリングできない領域でのメモリ占有率が大きくなります。

例えばTypeORMだと、

import{Entity,Column,PrimaryGeneratedColumn,OneToMany,ManyToOne,getManager}from"typeorm"@Entity()classOrder{@PrimaryGeneratedColumn("increment")publicid:number@Column({type:"varchar",length:127})publicfieldA:string// ...@Column({type:"varchar",length:127})publicfieldZ:string@OneToMany(()=>OrderItem,item=>item.order)publicitems?:OrderItem[]}@Entity()classOrderItem{@PrimaryGeneratedColumn("increment")publicid:number@Column({type:"int"})publicorderId:number@Column({type:"varchar",length:127})publicfieldA:string// ...@Column({type:"varchar",length:127})publicfieldZ:string@ManyToOne(()=>Order,order=>order.items)@JoinColumn({name:"order_id"})publicorder?:Order}constorders=awaitgetManager().createQueryBuilder().select("o").from(Order,"o").innerJoinAndSelect("o.items","oi").where("o.id <= :lastId",{lastId:50000}).getMany()

のように書けるわけですが、このとき最終的に受け取ったordersEntityデコレータで定義したclass Orderのインスタンスとなっています。
order.itemsのようにOrderItemにもアクセスすることができるようになっています。

DBからドライバを通じて受け取れるのはフィールドごと、もしくは行ごとのデータなので、ここまで変換するのがORMの仕事です。

ただこのクエリ、よく見ると5万件ほどのオーダーを取得しています。
さらにオーダーアイテムもJOINされているので、割と地獄のようなレコード数になりそうです。

ORマッピングの仕様は各ORM次第ですが、僕が使っているTypeORMでは少なくとも

  • 検索条件に合致する全レコードを配列の形で取得する
  • 全レコード配列をイテレートしてインスタンスの配列になおす

という、当たり前ですが非常に愚直な処理を行っています。
仮に1件のオーダーに平均10のアイテムが紐づくものとすれば、レコード数は500000件です。

50万件分の配列がメモリに読み込まれ、最低でも50万回のイテレーションが行われるわけですね。
この量の配列になると、数百MB~数GBに及ぶ場合もあります。

これだけでもAPIに多大なる負荷をかけそうですが、場合によって、ここで取得できたordersに対して更に何らかの処理を加えたい場合などもあります。

そうなると、せっかくORMが加工してくれた50万件のインスタンスを再度イテレーションする。。。
ということで、輪をかけて負荷が上昇します。

このような場合、ORMの変換処理に頼らず、自ら変換処理を書くことも一つの手段となります。

例えばTypeORMの場合、getManyではなくgetRawManyというメソッドが用意されており、後者はインスタンスへの変換を行わず、DBから取得されたレコードの配列をそのまま返却します。

必要であれば、それらのレコードに対して自前の変換処理を書くことにより、APIへの負荷を軽減することができます。

メモリに一度に読み込まれるデータ量を減らす

これは上記のもう1段階発展系ですが、DBからの出力を「全てメモリに読み込む」のではなく、「1行ずつメモリに読み込む」ことによりメモリへの負荷を劇的に軽減させることができます。

Nodeには標準APIとしてStream APIが組み込まれており、かつTypeORMにはDBからのローデータをReadStreamとして返却する.stream()メソッドが用意されています。

これらを活用すると、下記のようなコードが書けます。

import{Transform,TransformCallback}from"stream"/* 中略 */constqueryBuilder=getManager().createQueryBuilder().select("o")// <- 本当は絞ったほうが良いけど例なので許してください.from(Order,"o").innerJoinAndSelect("o.items","oi")// <- 本当は絞ったほうが良いけど例なので許してください.where("o.id <= :lastId",{lastId:50000})// @NOTE: 自前のクエリランナーを用意してやらないと、クエリが実行される前にコネクションが閉じてしまう。バグ?constqueryRunner=queryBuilder.connection.createQueryRunner()constdbReadStream=awaitqueryBuilder.setQueryRunner(queryRunner).stream()awaitnewPromise((resolve,reject)=>{dbReadStream.pipe(newTransform({objectMode:true,transform(chunk:any,encoding:string,cb:TransformCallback){// ここでchunk(= DBからのRawDataPacket)に対する変換処理を行うcb()}flush(cb:TransformCallback)=>{// @NOTE: chunkの最終行が読み込まれた後に発火するcb()}})).on("finish",()=>{// @NOTE: 自前のクエリランナーは自分で解放する必要があるqueryRunner.release()resolve(result)// <- ここで変換後のデータを返す}).on("error",(err)=>{// @NOTE: 自前のクエリランナーは自分で解放する必要があるqueryRunner.release()reject(err)})})

TypeORMから返却されたReadStreamに対してTransformStreamを接続(pipe)し、任意の変換を行います。
この処理をPromiseでラップし、変換が完了(finish)ないし失敗(error)した場合にそれぞれresolve, rejectしてやることで、パケットの逐次処理を行いつつ、それらの処理をawaitすることができています。

可読性は当然下がりますが、場合によってはこのような実装を行う必要も出てくると思います。

まとめ

大量データのロードによるヒープアウトでのAPI死亡をできる限り避けるためには,,,

  • V8エンジンに明示的にメモリを割り当てておく
  • そもそもその大量データを1度に取得する必要があるのか再検討、可能であればページネーションなど実装する
  • DBからの取得データ総量を減らすようにクエリをチューニング・修正する
  • ORマッパーに変換処理を任せるべきかどうかを判断する
  • メモリへの負荷が少ない処理を実装する

あたりが解決策になるかと思います。

もし「他にもこんなことできるよ」というのがありましたら是非教えて下さい m(_ _)m

axiosの概要

$
0
0

axios

axiosとは
ブラウザやnode.js上で動くPromiseベースのHTTPクライアント。非同期にHTTP通信を行いたい時容易に実装することができる。

GET通信
axios.getメソッドを使用する。
第一引数にURLを指定、then()で通信に成功した際の処理をかく。catch()でエラー時の処理を書く。
response.dataにデータが返る。

POST通信
axios.postメソッドを使用する。
POSTデータはaxios.postの第2引数で渡す。

レスポンスの構造

node.js
// レスポンス構造.axios.get('http://localhost:3000/users').then(function(response){console.log(response.data);// レスポンスデータconsole.log(response.status);// ステータスコードconsole.log(response.statusText);// ステータステキストconsole.log(response.headers);// レスポンスヘッダconsole.log(response.config);// コンフィグ});

Node.jsでglTFモデルを圧縮してthree.jsで読む込む(DRACO/meshoptimizer)

$
0
0

概要

WebGL表現でハイポリ(30K ~ 140K Poly)のglTFモデルを複数使用した際に、転送量やローディングパフォーマンスの観点からできる限りファイルサイズを落としたくて、Node.jsでglTFファイルを圧縮する環境を作ったのでその紹介です。

リポジトリはこちら

glTFのファイルサイズを軽量化する方法はいくつかありますが、本記事ではDRACOmeshoptimizerによる2通りの圧縮方法を紹介します。

環境構築

yarnとNode.jsのバージョンは以下です。

yarn@v1.22.5
node@v12.21.0

プロジェクトディレクトリを作成、移動してpackage.jsonを生成します。

$ mkdir gltf-compressor
$ cd gltf-compressor
$ yarn init -y

srcディレクトリを作成しglTFを格納します。

$ mkdir src

今回はサンプルデータとして、mixamoからダウンロードしたアニメーション付きXBOTのモデルがどのくらい軽量化できるか比較してみます。

mixamo.gif

mixamoからダウンロードできるのは.fbx.dae形式なので、BlenderでglTFにエクスポートしておきます。

アニメーションマテリアル頂点ポリゴン
1228,31249,112
gltf-compressor
├── package.json
└── + src
    └── + Capoeira
        ├── + Capoeira.bin
        └── + Capoeira.gltf

圧縮前のglTFは2.3MBありました。

before.png

DRACO圧縮

DRACO圧縮にはgltf-pipelineパッケージを使用します。

gltf-pipelinefs-extraglobpathをインストールし、DRACO圧縮用のJSファイルcompress-draco.jsを作成します。

$ yarn add gltf-pipeline fs-extra glob path
$ touch compress-draco.js
gltf-compressor
├── + compress-draco.js
├── node_modules
│   └── ...
├── package.json
├── src
│   └── ...
└── yarn.lock

yarn dracoコマンドを実行した際に対応するJSが実行されるように、package.jsonscriptsを追加します。

package.json
{"name":"gltf-compressor","version":"1.0.0","main":"index.js","license":"MIT","scripts":{"draco":"node compress-draco.js"},"dependencies":{"fs-extra":"^9.1.0","glob":"^7.1.6","gltf-pipeline":"^3.0.2","path":"^0.12.7"}}

Node.jsでDRACO圧縮をするコードです。

compress-draco.js
constglob=require('glob');constfs=require('fs-extra');constpath=require('path');constgltfPipeline=require('gltf-pipeline');constsrcDir='src';constdistDir='dist';/**
 * glTFをDRACO圧縮
 * @param {string | string[]} globs 
 */constcompressGltfWithDraco=(globs)=>{glob(globs,async(err,files)=>{if(err)return;for(constfileoffiles){constfilePath=path.resolve(file);constgltf=fs.readJsonSync(filePath);// gltfのJSONを読み込むconstoptions={resourceDirectory:path.dirname(filePath),// gltfのリソースディレクトリ(親フォルダ)dracoOptions:{compressionLevel:10}// DRACO圧縮率MAX};const{glb}=awaitgltfPipeline.gltfToGlb(gltf,options);// gltf -> glbconstoutFilePath=filePath.replace('.gltf','-draco.glb').replace(srcDir,distDir);// 出力先awaitfs.mkdirp(path.dirname(outFilePath));// distディレクトリがなかったら作成awaitfs.writeFileSync(outFilePath,glb);// glbファイル出力console.log(`[draco] ${outFilePath}`);}});};compressGltfWithDraco(`./${srcDir}/**/*.gltf`);

globsrcフォルダ内にある.gltfファイルの配列を取得し、.gltfJSONとして読み込ませたものとオプションをgltfPipeline.gltfToGlb()に渡して圧縮します。

glTFを圧縮しているのは以下の部分です。

constoptions={resourceDirectory:path.dirname(filePath),// gltfのリソースディレクトリ(親フォルダ)dracoOptions:{compressionLevel:10}// DRACO圧縮率MAX};const{glb}=awaitgltfToGlb(gltf,options);// gltf -> glb

用意しておいたコマンドを実行してDRACO圧縮をかけます。

$ yarn draco

distディレクトリにDRACO圧縮後の.glbファイルが出力されます。

gltf-compressor
├── compress-draco.js
├── + dist
│   └── + Capoeira
│       └── + Capoeira-draco.glb
├── node_modules
│   └── ...
├── package.json
├── src
│   └── Capoeira
│       ├── Capoeira.bin
│       └── Capoeira.gltf
└── yarn.lock
BeforeAfter圧縮率
2.3MB804KB-64.79%

after-draco.png

three.jsで読み込む

GLTFLoader.setDracoLoader()DRACOLoaderのインスタンスを渡して読み込みます。
DRACO圧縮のデコードに必要なdraco_decoder.jsdraco_decoder.wasmthree/examples/js/libs/dracoに用意されているので、自分のサーバーにコピーしてDRACOLoader.setDecoderPath()でパスを指定する必要があります。

参考:https://threejs.org/docs/index.html#examples/en/loaders/GLTFLoader

import{GLTFLoader}from'three/examples/jsm/loaders/GLTFLoader';import{DRACOLoader}from'three/examples/jsm/loaders/DRACOLoader';constdracoLoader=newDRACOLoader();dracoLoader.setDecoderPath('/path/to/draco_decoder/');constloader=newGLTFLoader();loader.setDRACOLoader(dracoLoader);loader.load('Capoeira-draco.glb',(gltf)=>{scene.add(gltf.scene);});

meshopt圧縮

meshopt圧縮にはgltfpackパッケージを使用します。

gltfpackをインストールして、meshopt圧縮用のJSファイルcompress-meshopt.jsを作成します。

$ yarn add gltf-pack
$ touch compress-meshopt.js
gltf-compressor
├── compress-draco.js
├── + compress-meshopt.js
├── dist
│   └── ...
├── node_modules
│   └── ...
├── package.json
├── src
│   └── ...
└── yarn.lock

yarn meshoptコマンドを実行した際に対応するJSが実行されるように、package.jsonscriptsを追加します。

package.json
{"name":"gltf-compresser","version":"1.0.0","main":"index.js","license":"MIT","scripts":{"draco":"node compress-draco.js","meshopt":"node compress-meshopt.js"},"dependencies":{"fs-extra":"^9.1.0","glob":"^7.1.6","gltf-pipeline":"^3.0.2","gltfpack":"^0.15.2","path":"^0.12.7"}}

Node.jsでmeshopt圧縮をするコードです。

compress-meshopt.js
constcp=require('child_process');constglob=require('glob');constgltfPack=require('gltfpack');constfs=require('fs-extra');constpath=require('path');constsrcDir='src';constdistDir='dist';constpaths={'basisu':process.env['BASISU_PATH'],'toktx':process.env['TOKTX_PATH']};/**
 * gltfpack用インターフェース
 */constgltfPackInterface={read:(path)=>{returnfs.readFileSync(path);},write:(path,data)=>{fs.writeFileSync(path,data);},execute:(command)=>{// perform substitution of command executable with environment-specific pathsconstpkv=Object.entries(paths);for(const[k,v]ofpkv){if(command.startsWith(k+'')){command=v+command.substr(k.length);break;}}constret=cp.spawnSync(command,[],{shell:true});returnret.status==null?256:ret.status;},unlink:(path)=>{fs.unlinkSync(path);}};/**
 * compress gltf -> glb
 * @param {string} inputPath 
 * @param {string} outputPath 
 * @return {Promise<string>}
 */constpackGltf=(inputPath,outputPath)=>{constoutput=outputPath||inputPath.replace('.gltf','.glb');constcommand=`-i ${inputPath} -o ${output} -cc`;// コマンドライン引数(必要に応じてオプションを追加)constargs=command.split(/\s/g);// コマンドライン引数の配列returngltfPack.pack(args,gltfPackInterface).catch(err=>{console.log(err.message);});};/**
 * glTFをmeshoptimizer圧縮
 * @param {string | string[]} globs 
 */constcompressGltfWithMeshopt=(globs)=>{glob(globs,async(err,files)=>{if(err)return;for(constfileoffiles){constfilePath=path.resolve(file);constoutFilePath=filePath.replace('.gltf','-meshopt.glb').replace(srcDir,distDir);// 保存先awaitfs.mkdirp(path.dirname(outFilePath));// distディレクトリがなかったら作成awaitpackGltf(filePath,outFilePath);// gltf -> glbconsole.log(`[meshopt] ${outFilePath}`);}});};compressGltfWithMeshopt(`./${srcDir}/**/*.gltf`);

glTFひとつを圧縮するpackGltf関数を用意して、globで取得したファイルリストに対して回しています。

gltfPackInterfaceの部分はgltfpack/cli.jsの実装から引っ張ってきたので、なぜこういう実装になっているかわかりません。(拡張性をもたせるため?)

constpackGltf=(inputPath,outputPath)=>{constoutput=outputPath||inputPath.replace('.gltf','.glb');constcommand=`-i ${inputPath} -o ${output} -cc`;// コマンドライン引数(必要に応じてオプションを追加)constargs=command.split(/\s/g);// コマンドライン引数の配列returngltfPack.pack(args,gltfPackInterface).catch(err=>{console.log(err.message);});};

用意しておいたコマンドを実行してmeshopt圧縮をかけます。

$ yarn meshopt

distディレクトリにmeshopt圧縮後の.glbファイルが出力されます。

gltf-compressor
├── compress-draco.js
├── compress-meshopt.js
├── dist
│   └── Capoeira
│       ├── Capoeira-draco.glb
│       └── + Capoeira-meshopt.glb
├── node_modules
│   └── ...
├── package.json
├── src
│   └── Capoeira
│       ├── Capoeira.bin
│       └── Capoeira.gltf
└── yarn.lock
BeforeAfter圧縮率
2.3MB247KB-89.16%

after-meshopt.png

three.jsで読み込む

GLTFLoaderのDocumentationには記載されていませんが、GLTFLoader.setMeshoptDecoder()というメンバ関数が用意されていて、デコーダーもthree.js内に含まれています。

こちらはGLTFLoader.setMeshoptDecoder()MeshoptDecoderをそのまま渡します。

参考:https://threejs.org/examples/#webgl_loader_gltf_compressed

import{GLTFLoader}from'three/examples/jsm/loaders/GLTFLoader';import{MeshoptDecoder}from'three/examples/jsm/libs/meshopt_decoder.module.js';constloader=newGLTFLoader();loader.setMeshoptDecoder(MeshoptDecoder);loader.load('Capoeira-meshopt.glb',(gltf)=>{scene.add(gltf.scene);});

比較

圧縮形式BeforeAfter圧縮率
DRACO2.3MB804KB-64.79%
meshoptimizer2.3MB247KB-89.16%

今回のサンプルモデルだけで比較するとDRACOよりmeshoptimizerのほうが圧縮率が高かったです。

元ファイルのサイズが2.3MBもあったのに247KBまで落とせるというのはWebパフォーマンス的にもかなりありがたいですね。

いくつかモデルを圧縮して比較した印象では、モデルの作り方やジオメトリの構造によってはDRACO圧縮のほうがサイズを落とせる場合があったり、meshopt圧縮をすると一部のメッシュが破綻することがあったので、どちらか一択という訳にはいきませんでした。
使用するモデルの中身に応じて圧縮形式を適宜使い分ける必要がありそうです。

Links

gltf-pipeline
gltfpack
gltf-transform(試してないが多機能そうなnpmパッケージ)
ポリゴン数やテクスチャサイズなどを自動で最適化してくれるオンラインサービス(月20回までは無料)

Fire Storageを監視してサイレントプッシュ通知を送信

$
0
0

概要

Fire Storageに画像ファイルのアップロードがなされるとCloud Functionsを通してサイレントプッシュ通知を送信します。

今更感はありますが、ググってもpayloadの記述に統一性がなかったりして割と苦戦しました:sweat:

Stack OverFlowの方でも同じ悩みを抱えていた人がいたので参考になれば幸いです。

なお、本記事ではFirebase CLIや証明書周りについては触れません。
下準備ができている前提で進めます。

環境

  • Xcode 12.4
  • Swift 5
  • nodejs 12

Fire Storage

デフォルトバケットを使用

クライアント

Capability

TARGETS > CapabilitiesよりBackground ModesとPush Notificationsを有効にします。
Background ModesはRemote notificationsにチェックを入れます。
(バックグラウンドで通知を受け取りたい場合はBackground fetchもチェック)

Swift

今回はTopic購読で実装します。

AppDelegate.swift
importFirebase@mainclassAppDelegate:UIResponder,UIApplicationDelegate{funcapplication(_application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{FirebaseApp.configure()// 1. 通知許可letauthOptions:UNAuthorizationOptions=[.alert,.badge,.sound]UNUserNotificationCenter.current().requestAuthorization(options:authOptions,completionHandler:{(granted,error)in// todo})application.registerForRemoteNotifications()returntrue}funcapplication(_application:UIApplication,didRegisterForRemoteNotificationsWithDeviceTokendeviceToken:Data){// 2. Topic購読Messaging.messaging().subscribe(toTopic:"hoge"){_in// todo}}funcapplication(_application:UIApplication,didReceiveRemoteNotificationuserInfo:[AnyHashable:Any],fetchCompletionHandlercompletionHandler:@escaping(UIBackgroundFetchResult)->Void){// 3. 画像ファイルの名称を取り出すguardletimageName=userInfo["imageName"]as?Stringelse{return}print("image name: \(imageName)")}}

Cloud Functions

onFinalize関数でバケットに画像がアップロードされたことを検知します。

index.js
'use strict';constfunctions=require('firebase-functions');constadmin=require('firebase-admin');admin.initializeApp(functions.config().firebase);exports.sendSilentNotificationWithTopic=functions.storage.object().onFinalize((object)=>{consttopic='hoge';constpayload={data:{imageName:object.name,// 画像名をセット},};constoptions={contentAvailable:true// サイレントプッシュはtrueにする};returnadmin.messaging().sendToTopic(topic,payload,options).then(function(response){returnconsole.log("Successfully sent message:",response);}).catch(function(error){returnconsole.log("Error sending message:",error);});});

firebase deployを実行してデプロイします。

実行

  1. Xcodeに実機をつないでRun
  2. Fire Storageに画像ファイルを追加

Xcodeのコンソールログに画像名が表示されれば成功です。
自分はこれで成功したのですが、失敗したらすいません。。。

Bootstrapをnodejsで利用するために調べたこと

$
0
0

BootStrap使ってカッコつけたい

今回、掲示板アプリ作成してます。
javascriptとhtmlとcssで出来ないこともないけど、せっかくならプラスオンして新しい技術に触れたいと思い、色々調べた結果bootstrapにたどり着きました。

ドキュメントを読んでサラッと使えると思いきや、
豪快につまづいてもがいていました。

「webpack」が必要みたい。

今回の引用と参考サイトはこちらです。
神の手が差し出された瞬間です。
とってもわかりやすかった。
最新版で学ぶwebpack 5入門 Bootstrapをバンドルする方法

webpackは簡単に言えば、複数のJavaScriptを1つにまとめること。
1つにまとめることができれば、htmlやejsへの適用もシンプルになります^^
javascriptだけではなく、sassファイルやcssファイルもloaderと呼ばれるモジュールをインストールすることで,webpackで1つにまとめることが出来る優れものです。

今回、bootstrapをnode.jsに適用するまでの3ステップをメモ

1.モジュールのインストール

npm i -D webpack webpack-cli style-loader css-loader

2.bootstrapを動かすためのライブラリをインストール

npm i -S bootstrap jquery popper.js

ドキュメントにはjqueryとpopperに依存しているので、言われるがままにインスト

3.webpackの設定と実行

webpack.config.jsにファイルの処理方法を記述します。
ここでは、単純に調理方法を記載していると考えてください。

module.exports={// モード値を production に設定すると最適化された状態で、// development に設定するとソースマップ有効でJSファイルが出力されるmode:"production",module:{rules:[{// 対象となるファイルの拡張子(cssのみ)test:/\.css$/,// Sassファイルの読み込みとコンパイルuse:[// スタイルシートをJSからlinkタグに展開する機能"style-loader",// CSSをバンドルするための機能"css-loader"],},],},};

次に食材を用意します。

srcフォルダー内にindex.jsファイルを作成。
index.jsファイルにはimport文を用いて、Bootstrapを取り込みます。

// Bootstrapのスタイルシート側の機能を読み込むimport"bootstrap/dist/css/bootstrap.min.css";// BootstrapのJavaScript側の機能を読み込むimport"bootstrap";

最後に食材と調理方法が準備できれば早速調理開始です。

npx webpack

と実行すると、
'dist'フォルダが生成され、一つになったjsファイルが生成されます。
これを同じようにhtmlまたはejsファイルに取り込みすれば、環境設定は完了です。
bootstrapドキュメントのレイアウトのコピーを貼り付けすれば、使えるようになります。

まとめ

かなりざっくりすぎてビックリですが、忘備録として残しておきます。
webpackの記述方法は、後に身につけたいなと思います。

npmの復習

$
0
0

目次

1.この記事の目的
2.概要
3.パッケージ管理システム
4.もしnpmがなかったら
5.npmの使い方
6.便利なコマンド
7.最後に
8.参考記事


1. この記事の目的

  • npmに関する知識の復習

    • もやっとした理解だったため記事を読んで理解を深め言語化する
  • 実務でも使えそうな+αの知識を紹介
     - 調べる中で、便利なコマンドなどを学べたので紹介

  • 読んでくれる人の理解の手助けになれば


2. 概要

npmとは?

「Node Package Manager」の略。
Node.jsのパッケージ管理を行うツール。

Node.js

サーバーサイドで動くJavaScript。

特徴

  • 大量のデータ処理が可能
    • C10K問題に対応
      • Apache HTTP Serverなどウェブサーバーとクライアント通信においてクライアントが約1万台に達するとウェブサーバーのハードウェア性能に余裕があるにもかかわらず、レスポンス機能が大きく下がる問題
  • メモリ消費が少ない
    • イベントループ(詳細省略)
  • 処理速度が速い
    • Google V8 JavaScript Engine
      • Googleが開発したコンパイルエンジン
      • Chromeなどで使用
      • 中間コード、インタープリタを搭載せず、最初の実行時からコンパイルされる
      • JIT(Just-In-Time Compiler)
        • ソフトウェアの実行時にコードのコンパイルを実行。実行速度の向上を図るコンパイラ
        • (AOTコンパイラ)
          • 事前コンパイラ
  • サーバーサイドでJavaScriptが使える
    • 「サーバーサイドは新しい言語」ではなく、クライアント側で使用するJavaScriptをそのままサーバーサイドで使用できるため基礎的な書き方は同じ。

パッケージ

あらかじめ用意された便利な機能をまとめたもの

代表例

  • Express
    • Node.jsのMVCフレームワーク
  • promiss、async
    • 非同期処理

など


3. パッケージ管理システム

一つの環境で各種ソフトウェアの導入や削除、依存関係を管理するシステムのこと。

機能

主に4つの機能がある!

  • リポジトリの購読

    • インターネット上のパッケージリポジトリを指定し、必要に応じてパッケージリストを更新
  • ソフトウェアパッケージのインストール・削除

    • ソースコードや設定ファイルをパッケージ化してインストール可能
  • 依存関係の解決

    • あるパッケージに必要なソフトウェアを自動的にインストールしたり更新することができる
    • 同時にインストール、更新できないソフトウェアパッケージのインストールや更新を回避できる
  • 設定管理

    • 設定スクリプトを使って自動的に設定を行える
      • npmならpackage.json、composerならcomposer.jsonとか
    • 競合する機能を持つソフトウェアで、どちらを優先して使うかを設定できる

言われてみたら「確かにな」って感じるけど、言語化するとしっくりくる。


4. もしnpmがなかったら

開発するのに色々手間暇時間がかかります。
→「手間暇かけるのが愛情」が正だとすると、別のところに愛情を使えるようになる

  • 使用するパッケージ、ライブラリなどをいちいち公式サイトなどからダウンロードしないといけない

  • 作ったnpmパッケージをチームで共有したいが共有や更新などの管理などが面倒

つまり、楽に速くコードを書くことができるようになりサービスの開発時間の短縮や他のことに時間をかけられるようになる。


5. npmの使い方

Node.jsのダウンロード

公式サイトからダウンロード。
Node.jsをダウンロードしたらnpmもダウンロードされます

バージョンの確認

ダウンロードしたら

terminal
npm --version

でnpmのバージョンを確認します
(npmのパスが通ってなかったら通してください)

使用するパッケージをインストールする

ここでは例でjQueryをインストールします

プロジェクトルート配下
npm install jquery -g

※ 「-g」などのオプションは後述

既にpackage.jsonがあるプロジェクトの場合

package.jsonに記述されているパッケージを復元(インストール)します
(package.jsonはパッケージ管理スクリプトの一つ。詳細は後述)

package.jsonがディレクトリ
npm install

パッケージの更新

  • 未更新パッケージの確認
未更新パッケージを確認
npm outdated

※パッケージの最新バージョンを使用するにはpackage.jsonに記述が必要※

パッケージを更新する際、いちいちpackage.jsonに内容を確認、記述するのが面倒。
→パッケージのバージョンを一括で最新にしてくれるパッケージnpm-check-updatesが便利!

npm-check-updatesをインストール
npm install -g npm-check-updates
npm-check-updates -u
インストール後、アップデートで最新になる
npm update

これでnpmパッケージのバージョン管理が楽になる。


オプションについて

たくさんあるけど、この資料で使う分を紹介。

-g

グローバルインストール。どのディレクトリでも使えるようにパッケージをインストールする
(グローバルインストールについては後述)

-g
npm install jquery -g

--save

package.jsonのdependenciesに依存関係が追加される。
要は「このpackage.jsonが入ったプロジェクトに必要なパッケージ」
(dependenciesについては後述)

--save
npm install jquery --save

--save-dev

package.jsonのdevDependenciesに依存関係が追加される。
要は「パッケージの開発をするときに必要なパッケージ」。テストするためのパッケージとか。
(devDependenciesについても後述)


ここでnpmの説明から離れた小休憩。

グローバルインストールとローカルインストールの違い

恥ずかしながら、二つの違いをはっきり理解できていなかったため復習。

グローバルインストール

npm installするときに「-g」オプションをつけることで実行可能。
「npm」のルート配下にあるnode_modulesにパッケージがインストールされるため、一度グローバルインストールしたパッケージはどのディレクトリのプロジェクトでも使用することができる

ローカルインストール

npm installした時に何もオプションを付けなかった時はデフォルトでローカルインストールされる。
インストールされるパッケージの場所はプロジェクトルート配下にあるnode_modules。

従って、どのプロジェクトでも使いそうなパッケージをインストールするときはグローバルインストール、既存のプロジェクトを始めるとき(package.jsonが既にあったら)ローカルインストールの方が良い…のか?
※要確認


小休憩になるかわからないがnpmから直接離れてもう一度休憩。

package.jsonのdependenciesとdevDependencies

dependencies

npmパッケージを使うときに必要なパッケージ。
これがないとこのパッケージが動かない。
必要なパッケージ=依存しているパッケージ

dependenciesはインストールされる
npm install [パッケージ名]

これでもインストールされる。

devDependencies

npmパッケージを開発する時に必要なパッケージ。
package.jsonがあるディレクトリでnpm installをしないとインストールされない。
例えば
「jQuery」のパッケージをnpm installでインストールした時は「jQuery」を開発する上でのテスト関係のパッケージなどはインストールされない。
(パッケージをテストするためのパッケージ)


パッケージのアンインストール

グローバルインストールされたパッケージのアンインストール
グローバルインストールされたパッケージ
npm uninstall jquery -g
ローカルインストールされたパッケージのアンインストール
ローカルインストールされたパッケージ
npm uninstall jquery
package.jsonのdependenciesから依存関係を削除する場合
依存関係を削除
npm uninstall jquery --save
package.jsonのdevDependenciesから依存関係を削除
devDependenciesから削除
npm uninstall jquery --save-dev
ちなみに※uninstallは省略記号も使える
uninstallを省略
npm un jquery

6. 便利なコマンド

npm dedupe

パッケージの依存関係を整理してくれる.

  • モジュールBがCに依存

  • モジュールDがCに依存

    • 上記がそれぞれ別々の記述だった場合、記述が重複してバージョンが異なると不整合が起きる
  • これらを「モジュールB、DがCに依存」というふうに書き換えてくれるのがnpm dedupe

→パッケージをいくつか個別でinstallした後にやっておくと便利?

david

モジュールの最新安定バージョンをとってきてくれるツール
※依存モジュールの依存モジュールは最新にしない

david
npm install david -g
david update

オプションに-uを付けたら安定バージョンではない、最新のバージョンをとってきてくれるツール.


7. 最後に

参考記事を読んで僕なりにまとめてみましたが、もやっとしたところが言語化されて理解がスッキリした…かも。笑
特にグローバルインストールとローカルインストール、dependenciesとdevDependenciesははっきり理解できてなかったからとても勉強になりました。
あと便利コマンドも役に立ちそうだ!積極的に使って身につけていこう。

僕が下記の参考記事によって助けられたように
この記事が少しでも誰かの手助けになりますように。
ありがとうございました。


8. 参考記事

Wikipedia
- npm
- パッケージ管理システム

Node.js開発者なら知っておきたい便利なパッケージ12選

$
0
0

本記事は、Indrek Lasn氏による「12 Useful Packages Every Node.js Developer Should Know」(2020年9月2日公開)の和訳を、著者の許可を得て掲載しているものです。

Node.js開発者なら知っておきたい便利なパッケージ12選

毎日の生産性を上げるNodeパッケージ

Image for post

はじめに

Node.jsはコードの再利用にぴったりです。コードを再利用するための根幹となるのはNPMパッケージです。

NPMパッケージは、時間と労力を大幅に節約してくれます。日付ライブラリが必要ですか?パッケージがあります。ユーティリティライブラリが必要ですか?問題ありません。コードの問題解決が必要な時はいつでも、必要に応じたパッケージがあるでしょう。

これは、すべてのNode.js開発者が知っておくべきパッケージのリストです。このNPMパッケージは、時間を節約し、魔法のように解決する助っ人のようなものです。

1. husky

huskyはgitフックの実装を簡単にしてくれます。チームで仕事をしていて、チーム全体にコーディング規約を適用したいと思うことはありませんか?問題ありません!huskyを使えば、リポジトリにコミットしたりプッシュしたりする前に、コードを自動的にlintしてテストするように全員に要求できます。

Image for post
husky ― https://github.com/typicode/husky

インストール方法

yarn add husky

使い方

huskyフックの実装例です。

//package.json{"husky":{"hooks":{"pre-commit":"npm lint","pre-push":"npm test"}}}

huskyexample.json
huskyフック例

pre-commitフックは、リポジトリにコミットする前に実行されます。

pre-pushフックは、コードをリポジトリにプッシュする前に実行されます。

2. dotenv

dotenvは、環境変数を.envファイルからprocess.envにロードするゼロ依存モジュールです。コードとは別の環境に設定を保存することは、The Twelve-Factor Appの方法論に基づいています。
dotenv

インストール方法

yarn add dotenv

使い方

アプリケーションのできる限り早い段階で、dotenvを要求して設定します。

require('dotenv').config()

プロジェクトのルートディレクトリに.envファイルを作成します。NAME=VALUEの形式で、新しい行に環境固有の変数を追加します。

例:

DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3

process.envで、.envファイルで定義したキーと値が使えるようになりました。

const db = require('db')
db.connect({
   host: process.env.DB_HOST,
   username: process.env.DB_USER,
   password: process.env.DB_PASS
})

3. date-fns

lodashに似ていますが、date-fnsは日付のためのものです。日付の操作をもっと簡単にするユーティリティ関数がたくさんあります。

date-fnsは、ブラウザやNode.jsでJavaScriptの日付を操作するため、総合的かつシンプルで一貫性のあるツールセットを提供します。
date-fns

Image for post
date-fns ― https://github.com/date-fns/date-fns

インストール方法

yarn add date-fns

使い方

date-fnsライブラリの簡単な例です。

import { compareAsc, format } from 'date-fns'

format(new Date(2014, 1, 11), 'yyyy-MM-dd')
//=> '2014-02-11'

const dates = [
   new Date(1995, 6, 2),
   new Date(1987, 1, 11),
   new Date(1989, 6, 10),
]
dates.sort(compareAsc)
//=> [
//    Wed Feb 11 1987 00:00:00,
//    Mon Jul 10 1989 00:00:00,
//    Sun Jul 02 1995 00:00:00
// ]

その他の事例や使用例については、ドキュメントを確認してください。

4. bunyan

bunyan は、分かりやすくてパフォーマンスが高いNode用のJSONロギングライブラリです。

Image for post
bunyan ― https://github.com/trentm/node-bunyan

インストール方法

yarn add bunyan

コツbunyan CLIツールは、すべてのバージョンのbunyanのログと(合理的な範囲内で)互換性があるようになっています。そのため、yarn add global bunyanでグローバルbunyanをインストールしてbunyan CLIをPATHに追加してから、ローカルbunyanをインストールして、アプリでbunyanのNode.jsライブラリを使うと良いでしょう。

使い方

bunyanはシンプルで高速なNode.jsサービス用のJSONロギングライブラリです。

// hi.js
const bunyan = require('bunyan');
const log = bunyan.createLogger({name: "myapp"});
log.info("hi");

node hi.jsを実行するとコンソールに返される内容は、次の通りです。

Image for post

5. ramda

rambda は JavaScript プログラマ向けの実用的で機能的なユーティリティライブラリです。rambdaは、より純粋な機能性を重視しています。

不変性と副作用のない関数は、rambdaの設計哲学の中心です。これにより、シンプルで洗練されたコードで仕事を成し遂げられます。

Image for post
rambda ― https://github.com/ramda/ramda

インストール方法

$ yarn add ramda

使い方

import * as R from 'ramda'
const greet = R.replace('{name}', R.__, 'Hello, {name}!');
greet('Alice'); //=> 'Hello, Alice!'

上のコードの例です。

6. debug

debugはNode.jsコアのデバッグ手法をモデルにした、小さなJavaScriptデバッグユーティリティです。

Image for post
debug ― https://github.com/visionmedia/debug

インストール方法

$ yarn add debug

使い方

debugには、モジュール名を渡すだけでconsole.errorの装飾版を返し、debugステートメントを渡す関数があります。

constdebug=require('debug');constlog=debug('http:server');consthttp=require('http');constname='My App name';log('booting %o',name);http.createServer((req,res)=>{log(req.method+''+req.url);res.end('debug examplen');}).listen(3200,()=>{log('listening');});// run this command in the terminal// DEBUG=http:server node app.js

app-debug-example.js
debug例

これによりデバッグ出力を、モジュール全体だけでなく、モジュールのさまざまな部分で切り替えられます。

Image for post

7. flat

flat は、ネストされたJavascriptオブジェクトを取得して平坦化します。区切りキーを使って、オブジェクトの平坦化を解除することもできます。

Image for post
flat ― https://github.com/hughsk/flat

インストール方法

$ yarn add flat

使い方

const flatten = require('flat')

flatten({
   key1: {
      keyA: 'valueI'
   },
   key2: {
      keyB: 'valueII'
   },
   key3: { a: { b: { c: 2 } } }
})

// {
//    'key1.keyA': 'valueI',
//    'key2.keyB': 'valueII',
//    'key3.a.b.c': 2
// }

8. JSON5

JSON5 データ交換フォーマットはJSON* のスーパーセットで、ECMAScript 5.1のプロダクションの一部を含めるために構文を拡張し、JSONの制限の一部を緩和します。
JSON5

Image for post
json5 ― https://github.com/json5/json5

インストール方法

yarn add json5
const JSON5 = require('json5')

使い方

ファイル拡張子に注意してください。JSON5は、JSONの拡張機能とスーパーセットです。

{//commentsunquoted:'andyoucanquotemeonthat',singleQuotes:'Icanuse"double quotes"here',lineBreaks:"Look, Mom! \

No \\n's!",hexadecimal:0xdecaf,leadingDecimalPoint:.8675309,andTrailing:8675309.,positiveSign:+1,trailingComma:'inobjects',andIn:['arrays',],"backwardsCompatible":"with JSON",}

example.json5

9. ESLint

ESLintは、バグを回避し、開発チームにコーディング規約を適用する素晴らしいツールです。ECMAScript/JavaScriptコードで見つかったパターンを識別して報告します。

Image for post
ESLint ― https://github.com/eslint/eslint

インストール方法・使い方

$ yarn add eslint

次に、設定ファイルの作成が必要です。

$ ./node_modules/.bin/eslint --init

その後、任意のファイルやディレクトリでESLintを実行できます。

$ ./node_modules/.bin/eslint yourfile.js

詳しくは、公式ドキュメントを確認してください。始め方と設定の例がたくさんあります。

10. PM2

PM2は、Node.jsアプリケーション用のプロダクションプロセスマネージャーで、ロードバランサーを内蔵しています。これにより、アプリケーションを永続化させ、ダウンタイムなしでリロードし、一般的なシステム管理タスクを容易にできます。

Image for post
pm2 ― https://github.com/Unitech/pm2

インストール方法

$ yarn add global pm2

使い方

任意のアプリケーション(Node.js、Python、Ruby、$PATHのバイナリ等)を起動できます。

$ pm2 start app.js

アプリはデーモン化され、監視され、永続化します。プロセス管理の詳細はこちら

アプリケーション管理

起動したアプリケーションを簡単に管理できます。実行中のすべてのアプリケーションを一覧表示する方法は次の通りです。

$ pm2 ls

Image for post
pm2 ls

機能と活用方法の全リストは、公式ドキュメントを確認してください。

11. helmet

helmetライブラリは、さまざまなHTTPヘッダを設定することでExpressアプリを保護するのに役立ちます。「特効薬ではないが、助けにはなる!」

Image for post
helmet ― https://github.com/helmetjs/helmet

インストール方法

yarn add helmet

使い方

helmet はConnectスタイルのミドルウェアで、Expressなどのフレームワークと互換性があります(Koaのサポートが必要な場合は、koa-helmetを確認してください)。

const express = require("express");
const helmet = require("helmet");

const app = express();

app.use(helmet());

トップレベルのhelmetの関数は、11個の小さなミドルウェアのラッパーです。言い換えれば、この2つは同等です。

// This...
app.use(helmet());

// ...is equivalent to this:
app.use(helmet.contentSecurityPolicy());
app.use(helmet.dnsPrefetchControl());
app.use(helmet.expectCt());
app.use(helmet.frameguard());
app.use(helmet.hidePoweredBy());
app.use(helmet.hsts());
app.use(helmet.ieNoOpen());
app.use(helmet.noSniff());
app.use(helmet.permittedCrossDomainPolicies());
app.use(helmet.referrerPolicy());
app.use(helmet.xssFilter());

12. compression

compressionライブラリはNode.jsの圧縮ミドルウェアです。

Image for post
compression ― https://github.com/expressjs/compression

インストール方法

$ yarn add compression

使い方

このモジュールをexpressやconnectで使うには、expressミドルウェアでcompressionを呼び出すだけです。ミドルウェアを通過したリクエストは圧縮されます。

const compression = require('compression')
const express = require('express')

const app = express()

// compress all responses
app.use(compression())

// ...

おわりに

私のニュースレターでは、コンテンツの最新情報の通知を、いち早く受け取ることができます。ぜひ登録してください。

Zack Shapiroに謝意を表します。

翻訳協力

この記事は以下の方々のご協力により公開する事ができました。改めて感謝致します。

Original Author: Indrek Lasn (@lasnindrek)
Original Article: 12 Useful Packages Every Node.js Developer Should Know
Thank you for letting us share your knowledge!

選定担当: @gracen
翻訳担当: @gracen
監査担当: -
公開担当: @gracen

ご意見・ご感想をお待ちしております

今回の記事はいかがでしたか?
・こういう記事が読みたい
・こういうところが良かった
・こうした方が良いのではないか
などなど、率直なご意見を募集しております。
頂いたお声は、今後の記事の質向上に役立たせて頂きますので、お気軽に
コメント欄にてご投稿ください。Twitterでもご意見を受け付けております。
皆様のメッセージをお待ちしております。


【JavaScript】別ファイルに関数をまとめてexportsを利用して呼び出す。

$
0
0

開発環境

React.js

概要

JavaScriptで関数を共通化して別ファイルにまとめたときに、HTMLのscriptタグを使い呼び出すのは面倒だと思いました。JavaScriptのexportsを使い呼び出すことができることを知りましたので記事にしておきます。

import.jsとexport.jsファイルを作成

import.jsからexport.jsの関数を呼び出していきます。

export.js
exports.criminalIsKogoro=function(){console.log("犯人は毛利小五郎");}exports.detectiveCriminal=function(name){console.log("犯人は"+name);}constcriminalIsAgasa=function(){console.log("犯人は阿笠だ");}

比較するためにcriminalIsAgasaだけはexportsしないようにしておきます。

import.js
importexportFunctionfrom'export.jsのパスを記述'// または// var exportFunction = require('export.jsのパスを記述');exportFunction.criminalIsKogoro;// "犯人は毛利小五郎"exportFunction.detectiveCriminal("元太");//"犯人は元太"exportFunction.criminalIsAgasa;// error. exportFunction.criminalIsAgasa is not a function

このようにexportsをしておかないと外部のファイルから読み込むことができないことが分かりました。

参考

SlackでAPIを叩くときのボットのあれこれ

$
0
0

slackって?

Slack は、チームとコミュニケーションを図るための新しい手段です。メールよりも速く、整理され、安全な方法で実現できます。(公式から引用)

まあこんなQiitaみてるようなエンジニアに今更いうこともないので割愛させていただきます!

Botを作ってそいつに色々吐かせよう!

みなさんWebappとか作っててお問い合わせフォームとか作るじゃないですか。
その時にわざわざメアドで送らせてたりしてませんか?(煽り)

そんなのSlackのBotにやらせるのよ!

というわけで早速解説していこうと思うのですが、、、

あの〜〜フロント周りやバック周りは各自やってくださいね、、??

それでは本編に

SlackのAppの欄から”アプリを作る”を選択。
右上あたりにBotと書かれているアイコンがあると思うので、それをクリック。
そしたら名前つけたり、画像決めたりなどありますが、今回重要なのはそこではなく
スクリーンショット 2021-03-15 17.40.41.png

こいつがイッチバン大事!忘れないようにコピーしてメモにペースト!!!(まあ、忘れてもすぐ確認できるんだけどね)

そのあとは、ChannelのID!
こいつもないとBotがどこに吐き出していいかわからなくて困ってしまうよなあ??

確認方法(一番簡単だと思うもの)はWeb上でSlackに入り、チャンネルを開いたURLの最後のパスがIDになってるぜ。

終わりに

これで一応の設定は終わり。ほとんど備忘録として残しました。
みてくれてありがとう!!!!

nodeのhttp-serverをローカルにインストールして、起動する

$
0
0

とりあえずしたい事

普通にnodeで簡易的なサーバーを立てたいけど、グローバルにインストールするのはイヤなので、
ローカルに立てるよ。

 フォルダ構成はこうしたい

プロジェクトフォルダ/
   node_modules/
   html/
       index.html
       <ここにhtmlをおきたい>
  <nodeのプロジェクトでよく有る、あれやこれや>.json
  etc...

この状態でlocalhost:8080にアクセスしたら
index.htmlが表示するようにしたい

http-serverをインストールして使えるようにする。

インストールコマンド

npm install http-server

起動

node_modules\.bin\http-server

01.png

毎回こんなコマンド叩くの嫌なので、npm scriptに入れる

package.jsonに追加

{
  "name": "×××××",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "server":"node_modules/.bin/http-server ./html", //追加 htttpルートはhtmlフォルダになる。
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "http-server": "^0.12.3"
  }
}

起動はカレントディレクトリで

npm run server

02.png

Firestoreの複合インデックスでつまったところまとめ

$
0
0

対象読者

複合インデックスを登録しているのに取得できない方

解決方法

まず、エラー内容を確認します。firebase Functionsを使っている方はfirebaseのコンソールに入って、Functionsのログからエラーを確認してください。もし使われていない方は、例外処理によってエラー内容を返して確認してみてください。以下のエラー(一部抜粋)が出力されていると思います。

"details": "The query requires an index. You can create it here: https://console.firebase.google.com/v1/r/project/[projectID]/firestore/indexes?create_composite=[文字列]",

「クエリを実行するにはインデックスが必要なので作ってくださいね」と書かれており、親切にURLも記載してくれています:clap:。アクセスすると、Google Cloud PlatFormがブラウザで開き、インデックスを登録するかを聞かれますので、そのままインデックスを登録をしてください。
登録が成功された状態でAPIを実行すると、無事にデータが取得できるようになります。(この解決方法は公式ドキュメントにも記載されています。)

実際につまったところと原因

以下のような構造をとったデータが保存されているとします。

interface
interfaceLog{user_id:stringmessage_id:stringcreated_at:string}

そのデータを以下の条件で取得したいと仮定します。
①あるユーザーのログをメッセージごとに並び替え、さらに作成日の順に並び替えて取得する
②あるメッセージのログをユーザーごとに並び替え、さらに作成日の順に並び替えて取得する

model/index.js
// ①の場合のクエリsnapshot=awaitdocRef.where('user_id','==',userId).orderBy('message_id').orderBy('created_at').get()// ②の場合のクエリsnapshot=awaitdocRef.where('message_id','==',messageId).orderBy('user_id').orderBy('created_at').get()

このクエリに対し、以下のインデックスを事前に登録していました。

コレクションIDインデックス登録されるフィールドクエリのスコープ
collection_iduser_id 昇順 message_id 昇順 created_at 昇順コレクション

※表の列の名前は、それぞれfirebaseのコンソールでFirestoreにアクセスし、インデックスに書かれているものです。Google Cloud PlatFormで見た場合は
  インデックス登録されるフィールド → フィールド
  クエリのスコープ → Query scope
となっています。

この状態で実際にAPIを実行すると、①は取得できて②ではエラーになります。
そして、上記の解決方法に記載の通りクエリを作成すると、インデックスが以下のようになります。

コレクションIDインデックス登録されるフィールドクエリのスコープ
collection_iduser_id 昇順 message_id 昇順 created_at 昇順コレクション
collection_idmessage_id 昇順 user_id 昇順 created_at 昇順コレクション

ここで、もう一度APIを実行すると先ほどエラーだった②も取得できるようになります。
つまり、原因はクエリで指定している複数のインデックスをその順にインデックスに登録していなかったこととなります。今回のケースでは、使用したフィールドは同じでしたが、厳密には条件が異なっており、インデックスも別々に定義する必要があったようです。

このように条件が違えばインデックスを新たに定義すればいいと言うことが分かったのですが、やみくもにインデックスを作成していると利用料金(インデックスはストレージの料金として計上されるみたいです)が無駄にかかってしまうので、インデックス マージを活用して重複するインデックスを削減する、と言ったことも可能みたいです。


公式ドキュメントを読んでみて「複合インデックスを登録すると複数のフィールドを組み合わせたインデックスを作成でき、それによってデータが取得できるようになる」と自分なりに解釈をしてみたのですが、組み合わせであっても順番を担保しないといけないのは何故なのでしょうか、、詳しい方いらっしゃいましたら教えていただきたいです、、:pray:

DynamoDb:ローカル開発環境の構築について

$
0
0

aws公式サイトで「NoSQL Workbench」というデータベース管理ツールGUIがあるが、
テーブル作成・変更、データ登録・更新しづらいので、dynamodb-adminの構築方法を詳細します。

dynamodb-adminの詳しい情報は、以下を参照してください。
https://www.npmjs.com/package/dynamodb-admin

dynamodb-adminをインストール(グローバルで)

  npm install dynamodb-admin -g
※1 もしstrict-ssl問題が発生した場合、下記コマンドで無効にして、再度インストールしてください。
  npm config set strict-ssl false
※2 root権限が持ってなかったら、先頭にsudoを追加してください。

環境変数設定

export DYNAMO_ENDPOINT=http://localhost:8000

起動コマンド

  dynamodb-admin

ブラウザで下記urlをアクセス。

Viewing all 8820 articles
Browse latest View live