20190530のNode.jsに関する記事は13件です。

Callback 使いから Promise マスターにクラスチェンジするぞ!

現在私は、非同期処理をするときは callback を使っています。
ですが、ネストが深くなるにつれて混乱してしまいます。( ゚Д゚)<メダパニ‼

Node.js には promise という callback 地獄を解消するためのオブジェクトが用意されています。
promise での処理を覚えれば、見やすいコーディングが可能です!

…と、言うことは理解できているのですが、何故か未だにあやふやな promise 処理。
こりゃもう、身体で覚えるしかねぇ!( ゚Д゚)
憧れの PROMISE マスターに、なりたいな。ならなくちゃ。絶対になってやるー!(*‘∀‘)

幸い(?)なことに前回作った LINEWORKS の BOT 登録プログラムが callback 地獄になっているので、こちらを promise に書き換えてみたいと思います。

(前回の記事)API を使って LINEWORKS BOT を登録する

registerBot.js
const api_id = "api id";
const consumer_key = "consumer key";
const token = "server list token";
const account_id = "ryo_saeba@hunter.city";
const domain_id = 00000000;
const callback_url = "https;//www.xyz.jp/callback";

// トーク Bot のテナント登録
const request = require('request');
const uri_text = "https://apis.worksmobile.com/" + api_id;
let options = {
    uri: uri_text + "/message/registerBot/v4",
    headers: {
        "Content-type": "application/json",
        "consumerKey": consumer_key,
        "Authorization": "Bearer " + token
    },
    json: {
        "name": "test bot",
        "photoUrl": "https://developers.worksmobile.com/favicon.png",
        "description": "defeat the promise process bot",
        "managerList": [account_id]
    }
};
request.post(options, (error, response, body) => {
    if(body.errorMessage) return;
    let botNo = body.botNo;
    // トーク Bot のドメイン登録
    options.uri = uri_text + "/message/registerBotDomain/v3"
    options.json = {
        "botNo": botNo,
        "domainId": domain_id,
        "usePublic": true,
        "usePermission": false
    }
    request.post(options, (error, response, body) => {
        if(body.errorMessage) return;
        // メッセージ受信サーバー追加
        options.uri = uri_text + "/message/setCallback/v2";
        options.json = {
            "botNo": botNo,
            "callbackUrl": callback_url,
            "callbackEventList": ["text", "sticker", "image"]
        };
        request.post(options, (error, response, body) => {
            if(body.errorMessage) return;
            // トーク Bot からメッセージ送信
            options.uri =  uri_text + "/message/sendMessage/v2",
            options.json = {
                "botNo": botNo,
                "accountId": account_id,
                "content": {"type":"text","text":"BOT 登録が完了しました(^ω^)"}
            };
            request.post(options, (error, response, body) => {
                console.log(body);
                if(body.errorMessage) return;
                console.log("success");
            });
        });
    });
});

このコードを promise 処理したコードに置き換えていくのですが、このあとしばらく私の恥ずかしい失敗談が続きますので「お前の失敗なんざどうでもいいから成功したコードを見せてくれ!」という一般的なご意見をお持ちの方は、是非!読み飛ばして一番最後の完成コードをご確認くださいませ。

promise に置き換えるには(失敗談)

さぁ、それではこの callback たちを promise に置き換えてみましょう!
「簡単だよ! .then でつなげばいいんだよ!」という先輩エンジニアのアドバイスの元、よく理解していないまま私が書いたコードがこちらです!

registerBot.js
// パラメータ等は前回と同じなので割愛
const request = require('request');
const uri_text = "https://apis.worksmobile.com/" + api_id;

// トーク Bot のテナント登録
new Promise((resolve,reject) => {
    request.post(options, (error, response, body) => {
        (body.errorMessage) ? reject() : resolve(body.botNo);
    })
}).then((botNo) => { // トーク Bot のドメイン登録
    options.uri = uri_text + "/message/registerBotDomain/v3"
    options.json = {
        "botNo": botNo,
        "domainId": domain_id,
        "usePublic": true,
        "usePermission": false
    }
    request.post(options, (error, response, body) => {
        (body.errorMessage) ? reject() : resolve(botNo);
    })
}).then((botNo) => { // メッセージ受信サーバー追加
    options.uri = uri_text + "/message/setCallback/v2";
    options.json = {
        "botNo": botNo,
        "callbackUrl": callback_url,
        "callbackEventList": ["text", "sticker", "image"]
    };
    request.post(options, (error, response, body) => {
        (body.errorMessage) ? reject() : resolve(botNo);
    })
}).then((botNo) => { // トーク Bot からメッセージ送信
    options.uri =  uri_text + "/message/sendMessage/v2",
    options.json = {
        "botNo": botNo,
        "accountId": account_id,
        "content": {"type":"text","text":"BOT 登録が完了しました(^ω^)"}
    };
    request.post(options, (error, response, body) => {
        if(body.errorMessage) reject();
        console.log("success");
        resolve();
    });
});

今見ると、ひっどいですねぇ~。恥さらしですな。
のちに、このコードを見た先輩が「なんでこうなるのか、本気でわけわからん」とマジ顔でおっしゃられました。
きっと、今この記事を読んでいる皆様も同じ顔をしているのでしょう。
恥ずかしさのあまり、つい山籠もりをしてしたくなりますが、後回しにして先に進みます。

上記コードですが、もちろんエラーになりました!(/・ω・)/

ReferenceError: reject is not defined

ようは「なんで何度も reject してんの?」ってことです。
コンピュータさん、頭の悪い子でごめんなさい。

promise にとって、reject は一回こっきり。
最初に new するときだけなのです。

元のコードでやっていたエラー処理を return から単に reject に置き換えたのがいけない。
理解が浅いからこういうことになるのです。
まったく恥ずかしいやつですね!(/ω\)

.catch でエラー処理をする

ってなわけで、たくさんの reject をひとつにすべく .catch でエラー処理をしていきます!
promise オブジェクトでは .catch でエラー処理いっぺんに指定できてしまうんです!
なんて素晴らしい仕様!
処理が複雑になり、.then の数が増えていっても、エラー処理は1つだけ。
わかりやすいし、見やすいですね~。
ってなわけで .catch を追加してエラー処理を1つにまとめたものがコチラ。

registerBot.js
// パラメータ等は前回と同じなので割愛
const request = require('request');
const uri_text = "https://apis.worksmobile.com/" + api_id;

// トーク Bot のテナント登録
new Promise((resolve,reject) => {
    request.post(options, (error, response, body) => {
        (body.errorMessage) ? reject() : resolve(body.botNo);
    })
}).then((botNo) => { // トーク Bot のドメイン登録
    options.uri = uri_text + "/message/registerBotDomain/v3"
    options.json = {
        "botNo": botNo,
        "domainId": domain_id,
        "usePublic": true,
        "usePermission": false
    }
    request.post(options, () => {});
}).then((botNo) => { // メッセージ受信サーバー追加
    options.uri = uri_text + "/message/setCallback/v2";
    options.json = {
        "botNo": botNo,
        "callbackUrl": callback_url,
        "callbackEventList": ["text", "sticker", "image"]
    };
    request.post(options, () => {});
}).then((botNo) => { // トーク Bot からメッセージ送信
    options.uri =  uri_text + "/message/sendMessage/v2",
    options.json = {
        "botNo": botNo,
        "accountId": account_id,
        "content": {"type":"text","text":"BOT 登録が完了しました(^ω^)"}
    };
    request.post(options, () => {});
}).catch((err) => { // エラー処理
    console.log(err);
});

おー!見やすくなったぞー(*´▽`)
などと最初は喜んだおバカな私。もちろんエラーになりましたとさ。ぎゃふん。

TypeError: Cannot read property 'botNo' of undefined

まぁ、ちゃんとエラーが catch できていたので良しとしましょう。

return で次の .then に値を渡す

さて、肝心のエラー内容ですが「botNo が空っぽだよ」と言われています。
そう、return していないので、次の .then に何にも渡していないんですよね!
超絶おバカですね。

return botNo;

return 文をそれぞれの .then に追加して、これで大丈夫だろう!と実行した私。
エラーメッセージ出なかったぞ!やったぁ!成功だ!

と、喜んだのも束の間。待てど暮らせど登録完了メッセージが送信されてこない。
「トーク Bot からメッセージ送信 API」の実行ログを見てみると、以下のエラーが。

{ errorMessage: 'Service fail, HTTP/1.1 400 Bad Request, {"code":400,"message":"Bad Request Parameters: Cannot access bot"}',
errorCode: '090',
code: 'SERVICE_UNAVAILABLE' }

ふむ。Bot にアクセスできないとな?・・・なんで?
これ、2つ目の「トーク Bot のドメイン登録」をしていないメッセージ送信すると起きる現象なんですよね。
試しにもう一度トーク Bot からメッセージ送信 API を叩いてみる。…成功。

つまり、だ。今はメッセージ送信ができて、登録時には、できない。
.then で非同期処理をしているはずなのに、ドメイン登録時の request の結果を待たずに次の .then に進んでいって、ドメイン登録完了前にメッセージを送信している?
つまーり!非同期処理になってないっ!!どうして!?

.then 内の request は結局 promise 化していない

どうしても何も、私がよく理解していないまま使ったのが悪いのです。反省。orz
色々と調べてみたところ、.then の中の request.post() は promise 化されていない模様。
promise 化されていないってことは非同期処理されていないってことで、もちろん順番はめちゃめちゃに。

よし、トーク Bot のテナント登録の際に promise 化した request を使おう!
と、そこでふと思った。いちいち request を promise 化するの、面倒くさくね?
面倒くさいということは!誰かが便利なものを作っているはず!

そうして私は request-promise と出会いました。先輩の皆様、ありがとうございます。
では、さっそく npm install request-promise してコードを書き替えていきたいと思います。

registerBot.js
// パラメータ等は前回と同じなので割愛
const request = require('request-promise');

let botNo;
// トーク Bot のテナント登録
request(options).then((body) => {
    botNo = body.botNo;
    // トーク Bot のドメイン登録
    options.uri = uri_text + "/message/registerBotDomain/v3"
    options.json = {
        "botNo": botNo,
        "domainId": domain_id,
        "usePublic": true,
        "usePermission": false
    }
    return request(options);
}).then((body) => {
    // メッセージ受信サーバー追加
    options.uri = uri_text + "/message/setCallback/v2";
    options.json = {
        "botNo": botNo,
        "callbackUrl": callback_url,
        "callbackEventList": ["text", "sticker", "image"]
    };
    return request(options);
}).then((body) => {
    // トーク Bot からメッセージ送信
    options.uri =  uri_text + "/message/sendMessage/v2",
    options.json = {
        "botNo": botNo,
        "accountId": account_id,
        "content": {"type":"text","text":"BOT 登録が完了しました(^ω^)"}
    };
    return request(options);
}).catch((err) => {
    console.log(err);
});

return request(options); に書き換えているので、botNo が次の .then に渡っていかないことに注意。
一番最初の bodybotNo を変数に入れておきましょう!

さぁ、実行してみましょう!ドン!

1558664439.png

だーいせーこーう♪ (*‘∀‘)<promise bot ゲットだぜ!

思った通りの動作をしてくれたので、良かった良かったホッと一息。
これで終わった!と、思いきや…。
実はある落とし穴があったことに、このときの私は気づいていなかったのだ。。。

エラー処理ができていない!?

さっき、.catch でエラー処理の設定をしました。
API ID や TOKEN などのパラメータの入力が間違っていた場合には、.catch に飛んでいってエラーを表示してくれるはず。
しかし、.catch には飛ばず、次の .then に進んでいってしまい、エラーを連発しているのです!

register-tenant
{ errorMessage: 'API ID not exists',
  errorCode: '052',
  code: 'NOT_FOUND' }
register-domain
{ errorMessage: 'API ID not exists',
  errorCode: '052',
  code: 'NOT_FOUND' }
register-callback
{ errorMessage: 'API ID not exists',
  errorCode: '052',
  code: 'NOT_FOUND' }
send-message
{ errorMessage: 'API ID not exists',
  errorCode: '052',
  code: 'NOT_FOUND' }

ナントイウコトデショウ

LINEWORKS API のエラーは .catch しない

これ、今回大変勉強になったところです。
例えばなんですが、request する際に uriNULL であるとか、パラメータの型指定が間違っているとかだと、ちゃんと .catch してくれるのですよ。
でも、LINEWORKS API からの返答がエラーだった場合には .catch してくれないのです。

request 自体は成功しているから、API がエラーだって言おうが request は成功してるもん!
ってことなんですね。

API に届いた時点で、成功。
その先でエラーだった場合には返ってきた値を見て .catch に投げる必要があるんですね。
try ~ catch における throw をする必要があるんですね。

throw しないで、reject しよう

JavaScript Promise の本に書かれていますが、promise オブジェクトを使うときは Promise.reject を使います。
body の中を見てエラーだったら Promise.reject(body); とすれば .catch に飛んでエラー表示してくれます。

LINEWORKS API からの Response がエラーかどうかを判別するには、errorCode または errorMessage が含まれているかを判定すれば良いでしょう。
なので、下記一文を諸所に追加します。

if(body.errorMessage) return Promise.reject(body);

これで、ついに完成となりました!

完成したコードです!

registerBot.js
const api_id = "api id";
const consumer_key = "consumer key";
const token = "server list token";
const account_id = "ryo_saeba@hunter.city";
const domain_id = 00000000;
const callback_url = "https;//www.xyz.jp/callback";

const request = require('request-promise');
const uri_text = "https://apis.worksmobile.com/" + api_id;

// トーク Bot のテナント登録
let options = {
    method: "POST",
    uri: uri_text + "/message/registerBot/v4",
    headers: {
        "Content-type": "application/json",
        "consumerKey": consumer_key,
        "Authorization": "Bearer " + token
    },
    json: {
        "name": "test bot",
        "photoUrl": "https://developers.worksmobile.com/favicon.png",
        "description": "defeat the promise process bot",
        "managerList": [account_id]
    }
};
let botNo;
console.log("register-tenant");
request(options).then((body) => {
    if(body.errorMessage) return Promise.reject(body);
    console.log(body);
    botNo = body.botNo;
    // トーク Bot のドメイン登録
    options.uri = uri_text + "/message/registerBotDomain/v3"
    options.json = {
        "botNo": botNo,
        "domainId": domain_id,
        "usePublic": true,
        "usePermission": false
    }
    console.log("register-domain");
    return request(options);
}).then((body) => {
    if(body.errorMessage) return Promise.reject(body);
    console.log(body);
    // メッセージ受信サーバー追加
    options.uri = uri_text + "/message/setCallback/v2";
    options.json = {
        "botNo": botNo,
        "callbackUrl": callback_url,
        "callbackEventList": ["text", "sticker", "image"]
    };
    console.log("register-callback");
    return request(options);
}).then((body) => {
    if(body.errorMessage) return Promise.reject(body);
    console.log(body);
    // トーク Bot からメッセージ送信
    options.uri =  uri_text + "/message/sendMessage/v2",
    options.json = {
        "botNo": botNo,
        "accountId": account_id,
        "content": {"type":"text","text":"BOT 登録が完了しました(^ω^)"}
    };
    console.log("send-message");
    return request(options);
}).then((body) => {
    if(body.errorMessage) return Promise.reject(body);
    console.log(body);
}).catch((err) => {
    console.log("error:")
    console.log(err);
});

うん、長い!( ゚Д゚)

おわりに

ここまでお付き合いいただきありがとうございました。いや、本当に。

ここまでの道のりも長かったし、比例して記事も長くなってしまいました。
結局、コードの長さ的には Callback と変わらなかったなぁ。

でも!ネストしていないから見やすいし、promise オブジェクトの構造がわかってると、こっちの方が拡張や修正はしやすいかも!

promise.finally とか Promise.all とか、まだ仲良くなれていない子たちがいるから、憧れの PROMISE マスターへの道のりはまだまだ遠いけど、諦めずに頑張りたいと思います!(^ω^)

ではまた!(^^)/

参考にさせていただきましたm(_ _)m

JavaScript Promiseの本
LINEWORKS Developers

JavaScriptの同期、非同期、コールバック、プロミス辺りを整理してみる
今更だけどPromise入門
CoffeeScriptでPromiseを使ったときにハマった
request-promiseを使ったHTTPクライアントを作る

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

Node.jsのジョブキューbull使用感メモ

github: https://github.com/OptimalBits/bull
npm: https://www.npmjs.com/package/bull

リトライ

デフォルトではリトライしない。
queueに対してではなく、jobをaddするときにオプションでattempsやbackoffを指定する。
https://github.com/OptimalBits/bull/blob/develop/REFERENCE.md#queueadd

Backoffはfixed(固定)の他にexponentialもある。

failedイベント はリトライの度に発火するので、
たとえばattempsを5としてずっと失敗すると5回走る。

今何回目のリトライなのかは job.attemptsMade で取得できる。
最終的に失敗したかどうかをハンドルする方法がわからない。
completedイベント は成功のときにしか発火しなかった。
errorイベント は全く発火しなかった。
以下のように試行回数を比較すれば一応できる。

queue.on('failed', job => {
    logger.debug(`job[${job.id}] failed ${job.attemptsMade} times`)
    if (job.attemptsMade === job.opts.attempts) {
        logger.error(`finally, failed job[[${job.id}]]`)
    }
})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ゼロから始める生体認証webアプリケーション作成(3)クライアント上での画像加工

はじめに

はじめに謝らせていただきます...
前回の記事で使ったopencv4nodejsですが....localホスト上で使う方法がわからなかったのでクビになりました。
代わりに本家opencvさんを使います。

1.node.js+express+jage環境構築

Expressの開発環境構築~デバッグ環境構築 ここを参考にしましょう。
私が書くより絶対わかりやすい。

スクリーンショット 2019-05-30 17.50.10.png

expressの中身はこんな感じになっています。
作ったスクリプトなどはpublicにHTML(拡張子が.jade似合っているけど...)はviewsに入れます。
他もいじれるけど、私みたいな初心者は触らないに限る。

2.スクリプトの修正

受け取った画像を100x100に縮小して、グレイスケール化します

processing_cv.js
et imgElement = document.getElementById('imageSrc');
let inputElement = document.getElementById('fileInput');
inputElement.addEventListener('change', (e) => {
  imgElement.src = URL.createObjectURL(e.target.files[0]);
  }, false);

imgElement.onload = function() {
  let input_img = cv.imread(imgElement);
  let resize_img = new cv.Mat();
  let dsize = new cv.Size(100, 100);
  // 100x100にリサイズ
  cv.resize(input_img, resize_img, dsize, 0, 0, cv.INTER_AREA);
  input_img.delete();

  let gray_img = new cv.Mat();
  //グレイスケール化
  cv.cvtColor(resize_img,gray_img, cv.COLOR_RGBA2GRAY, 0);
  cv.imshow('canvasOutput', gray_img);
  resize_img.delete(); gray_img.delete();
};

function onOpenCvReady() {
  document.getElementById('status').innerHTML = 'OpenCV.js is ready.';
}

前回とやっていることは同じですが....ずいぶん長くなってしまいます。このスクリプトの保存場所はpublic/javascripts/です。

3.layout設定

opencv公式チュートリアルにあったテンプレートを参考にjade仕様に書き換えました。

layout.jade
doctype html
html
  head
    meta(charset='utf-8')
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    h2 Hello OpenCV.js
    p#status OpenCV.js is loading...
    div
      .inputoutput
        img#imageSrc(alt='No Image')
        |     
        .caption
          | imageSrc 
          input#fileInput(type='file', name='file')
      |   
      .inputoutput
        canvas#canvasOutput
        |     
        .caption canvasOutput
    script(src="/javascripts/processing_cv4.js",type='text/javascript')
    script(async='', src='https://docs.opencv.org/3.4.1/opencv.js', onload='onOpenCvReady();', type='text/javascript')

4.表示の確認

localhost:3000に繋ぐとこんな感じに表示されます
スクリーンショット 2019-05-30 18.06.19.png

次に有名なlennaの画像を与えてみます。

スクリーンショット 2019-05-30 18.10.47.png

縮小してグレイスケール化した画像が出力されたことが確認できました。

まとめ

localhost上での画像加工を行うことができた。
次回は、少しjavascriptから離れて、顔認証システムを機械学習を用いて実装します。

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

ゼロから始める生体認証webアプリケーション作成(3)localhost上での画像加工

はじめに

はじめに謝らせていただきます...
前回の記事で使ったopencv4nodejsですが....localホスト上で使う方法がわからなかったのでクビになりました。
代わりに本家opencvさんを使います。

1.node.js+express+jage環境構築

Expressの開発環境構築~デバッグ環境構築 ここを参考にしましょう。
私が書くより絶対わかりやすい。

スクリーンショット 2019-05-30 17.50.10.png

expressの中身はこんな感じになっています。
作ったスクリプトなどはpublicにHTML(拡張子が.jade似合っているけど...)はviewsに入れます。
他もいじれるけど、私みたいな初心者は触らないに限る。

2.スクリプトの修正

受け取った画像を100x100に縮小して、グレイスケール化します

processing_cv.js
et imgElement = document.getElementById('imageSrc');
let inputElement = document.getElementById('fileInput');
inputElement.addEventListener('change', (e) => {
  imgElement.src = URL.createObjectURL(e.target.files[0]);
  }, false);

imgElement.onload = function() {
  let input_img = cv.imread(imgElement);
  let resize_img = new cv.Mat();
  let dsize = new cv.Size(100, 100);
  // 100x100にリサイズ
  cv.resize(input_img, resize_img, dsize, 0, 0, cv.INTER_AREA);
  input_img.delete();

  let gray_img = new cv.Mat();
  //グレイスケール化
  cv.cvtColor(resize_img,gray_img, cv.COLOR_RGBA2GRAY, 0);
  cv.imshow('canvasOutput', gray_img);
  resize_img.delete(); gray_img.delete();
};

function onOpenCvReady() {
  document.getElementById('status').innerHTML = 'OpenCV.js is ready.';
}

前回とやっていることは同じですが....ずいぶん長くなってしまいます。このスクリプトの保存場所はpublic/javascripts/です。

3.layout設定

opencv公式チュートリアルにあったテンプレートを参考にjade仕様に書き換えました。

layout.jade
doctype html
html
  head
    meta(charset='utf-8')
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    h2 Hello OpenCV.js
    p#status OpenCV.js is loading...
    div
      .inputoutput
        img#imageSrc(alt='No Image')
        |     
        .caption
          | imageSrc 
          input#fileInput(type='file', name='file')
      |   
      .inputoutput
        canvas#canvasOutput
        |     
        .caption canvasOutput
    script(src="/javascripts/processing_cv4.js",type='text/javascript')
    script(async='', src='https://docs.opencv.org/3.4.1/opencv.js', onload='onOpenCvReady();', type='text/javascript')

4.表示の確認

localhost:3000に繋ぐとこんな感じに表示されます
スクリーンショット 2019-05-30 18.06.19.png

次に有名なlennaの画像を与えてみます。

スクリーンショット 2019-05-30 18.10.47.png

縮小してグレイスケール化した画像が出力されたことが確認できました。

まとめ

localhost上での画像加工を行うことができた。
次回は、少しjavascriptから離れて、顔認証システムを機械学習を用いて実装します。

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

Webpack で ビルド時、環境ごとに読み込む環境変数を分けたい

概要

ほとんどの Laravel などのバックエンドフレームワークは環境変数ファイルが用意され、環境ごとに読み込まれる環境変数を動的に変えることができる機能が備わっています。
しかし、javascript の場合は環境変数を環境ごとに読み込む機能を自分で設定するか npm パッケージ を利用するしかなかったので、自分的ベストな方法をまとめておきます。

結論

Webpack の alias 機能を使えばパッケージやライブラリなしで環境変数を分けることができます!!!!!!

設定方法

今回編集するファイルは下記ファイルになります。

webpack.config.js
package.json

前提環境

開発環境は node サーバをローカルにたてて開発。
本番、ステージサーバは CI・CD を利用してビルド後デプロイするといった環境で行いました。

フォルダ構成

フォルダ構成は下記のとおり Webpack を利用した際の一般的な javascript の開発構成です。
環境変数をステージ毎に切り替えるための .env フォルダを用意しています。このフォルダにステージごとの環境変数を定義した js ファイルを用意します。

.env
  |_ dev.js
  |_ stg.js
  |_ prd.js
dist
  |_ main.js
src
  |_ index.js
node_modules
package-lock.json
package.json
webpack.config.js

環境変数ファイルの作成

.env フォルダ内に環境変数用の javascript ファイルを作成します。

dev.js
export default {
    apiUrl : 'https://dev-example.com'
}
prd.js
export default {
    apiUrl : 'https://example.com'
}

このような感じでステージごとの環境変数を設定していきます。

package.json の scripts 設定

package.json に npm run <ステージ>の実行コマンドを定義します。
ステージごとに環境変数定義ファイルを動的に読み込むために NODE_ENV=stg を設定します。
NODE_ENVwebpack.config.jsファイルで参照できる環境変数になります。環境変数の名前は何でも大丈夫です。
下記のようにステージごとに渡してあげる環境変数を変えてあげましょう。

package.json
{
  "scripts": {
    "stg": "NODE_ENV=stg webpack --mode production",
    "prd": "NODE_ENV=prd webpack --mode production",
    "start": "NODE_ENV=dev webpack-dev-server --mode development --hot --inline --watch-content-base"
  },
// 省略

webpack の設定

webpack.config.js に alias の設定を行います。

webpack.config.js
const environment = process.env.NODE_ENV || 'dev';

module.exports = {
  mode: 'development',

  resolve: {
    alias: {
      userEnv$: path.resolve(__dirname, `.env/${environment}.js`),
    },
  },
// 省略
}

alias は 深くネストされた javascript ファイルのパスをいちいち書くのがめんどくさい人用にエイリアスとして設定できるようにしてくれる Webpack の機能です。
この alias を利用してステージ毎に環境変数ファイルを動的に読み込むように設定しています。

process.env.NODE_ENV は package.json で定義した環境変数が取得できます。
この環境変数によって、読み込みたい環境変数が定義されたファイルを動的に変更します。
※環境変数を定義したファイルまでのパスは絶対パスで指定しましょう。

userEnv$: path.resolve(__dirname, `.env/${environment}.js`)

以上で設定は終わりです。

利用方法

javascript 側で環境変数を利用するときは下記のように import してあげましょう。

test.js
import userEnv from 'userEnv';
console.log(userEnv.apiUrl);

あとはローカルや CI・CDで npm コマンドを実行しましょう。

// ローカル開発環境
npm run start

// CI・CDスクリプト
npm run stg
npm run prd

これでステージごとに環境変数を読み込むことができました。
環境変数を動的に切り替えるパッケージが結構あるみたいですが alias を使う方法だとパッケージいらずに環境変数を切り替えられるので簡単に利用できると思います。

※ ちなみに node サーバではなくフロント側の環境変数の設定方法なので漏れてはいけない key などの情報は設定しないでね。全部漏れちゃいますので

環境変数用のパッケージに cross-envdotenvなどがありますがそれらを利用する場合の違いやメリット・デメリットがよくわかってないのでわかる人いたら教えてくださいm(_ _)m

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

Node.jsで文字列を暗号化・復号する 【string encryption in node.js】

どれだけニーズがあるのかわからないが、データベースに入ってるidとかを必要な時に難読化したかった。

例えば、example.com/1/profileみたいなURLだと、「1のところを2に変えたら他の人のプロフィール見えるんじゃないの」みたいなのをトライできないようにしたい。

で、とりあえずstringを入力したらそいつをencryptした文字列を返却するモジュールを作ってみよう。
加えて、Adminの立場的にはencryptされた文字列を見るのはめんどくさいのでdecryptもできるようにしよう。

nodeにはcryptoという暗号化周りの組み込みモジュールが存在するので、そいつを利用する。

ドキュメント: https://nodejs.org/api/crypto.html

const crypto = require('crypto');

const ENCRYPTION_KEY = "HH95XH7sYAbznRBJSUE9W8RQxzQIGSpy" // 32Byte. このまま利用しないこと!
const BUFFER_KEY = "RfHBdAR5RJHqp5wm" // 16Byte. このまま利用しないこと!
const ENCRYPT_METHOD = "aes-256-cbc" // 暗号化方式
const ENCODING = "hex" // 暗号化時のencoding

const rawString = JSON.stringify({userId: 1}) // 暗号化する対象。stringなら何でも。

function getEncryptedString(raw) {
  let iv = Buffer.from(BUFFER_KEY)
  let cipher = crypto.createCipheriv(ENCRYPT_METHOD, Buffer.from(ENCRYPTION_KEY), iv)
  let encrypted = cipher.update(raw)

  encrypted = Buffer.concat([encrypted, cipher.final()])

  return encrypted.toString(ENCODING)
}

function getDecryptedString(encrypted) {
  let iv = Buffer.from(BUFFER_KEY)
  let encryptedText = Buffer.from(encrypted, ENCODING)
  let decipher = crypto.createDecipheriv(ENCRYPT_METHOD, Buffer.from(ENCRYPTION_KEY), iv)
  let decrypted = decipher.update(encryptedText)

  decrypted = Buffer.concat([decrypted, decipher.final()])

  return decrypted.toString()
}

// 以下は試してみてるだけ

console.log(`raw: ${raw}`)

const encrypted = getEncryptedString(raw)

console.log(`encrypted: ${encrypted}`)

const decrypted = getDecryptedString(encrypted)

console.log(`decrypted: ${decrypted}`)

上記をコピペして適当なファイル名(encryption-sample.js とか)をつけて、node encryption-sample.jsを叩くと下記のようなアウトプットになるはず

raw: {"userId":1}
encrypted: e3629d8ff7295f693569359d06fd4da2
decrypted: {"userId":1}

今回は、getEncryptedString()に対してあるstring: Aを渡した場合に、どんな場合でも同じ結果string: A'が返ってきてほしかったので、let iv = Buffer.from(BUFFER_KEY)の部分で必ず同じiv (= initialization vector)が生成されるようにしています。

ただし、用途によっては毎回異なるivを生成し、encryption結果が毎回変わることが正な場合もある(というか本来の目的ではそちらのほうが正?)ので気をつけて下さい。

https://gist.github.com/vlucas/2bd40f62d20c1d49237a109d491974eb

それについては上記のコードが参考になると思います。
というか僕が参考にしました。

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

はじめての Nuxt.js

Nuxt はずっと前から気にはなってて、でも「今回は SSR しないからいいや」とか「今回は静的サイトジェネレーターで作るからいいや」とか思って避けてきたけど、なんか最近 Nuxt 使うって話を 3 回くらい聞いたので、ちょっと使ってみて構成とか眺めてみようと思う

先に使ってみた感想を述べておくと

  • Nuxt の公式ドキュメントがちゃんと作られているので、これだけ見れば大体解決した
  • 後述してますが TypeScript とか Vuex とかとても簡単に導入できるので感動した
  • webpack 周りが隠蔽されてる
    • 前に VueCLI v2 使ってみた時はけっこうビルド周りがむき出しになってた気がする(VueCLI v3 ではこの辺解決されてるっぽい)
  • Vue で SSR する時に Nuxt を使うという印象を持っていたが、SSR モードか SPA モードか選べたので考えが違った
  • フロントエンドチームが多いチームであれば、Angular などのガッチリ系フレームワークも検討しようと思っていたが、Vue が好きなら Nuxt でもいいかもしれない
    • TypeScript のサポートも昔よりはかなり良くなってて、Vue v3 がリリース(今年リリースされる?)されたら Vue 自体が TypeScript で作り直されるので、さらに Vue + Nuxt + TypeScript 環境が良くなっていくのではないかと思う

今回作業したリポジトリは以下
https://github.com/kurosame/nuxt-boilerplate

インストール

create-nuxt-appを使うと良さそう
npx でいけるとかすばらしい

npx create-nuxt-app nuxt-boilerplate

プロジェクト名は GitHub のリポジトリ名にした

? Project name nuxt-boilerplate
? Project description My groundbreaking Nuxt.js project
? Use a custom server framework express
? Choose features to install Progressive Web App (PWA) Support, Linter / Formatter, Prettier, Axios
? Use a custom UI framework vuetify
? Use a custom test framework jest
? Choose rendering mode Universal
? Author name kurosame
? Choose a package manager yarn

とりあえず全部入れといたが、以下に該当する場合はインストールから除外してもいいと思う
(でも環境を CLI ツールで管理する時点で環境周りをメンテナンスすることは基本的に無いと思うので、全部入れてしまっても問題無いと思う)

  • SSR しない
? Use a custom server framework
None
? Choose rendering mode
SPA
  • モバイル対応しない
? Choose features to install
Progressive Web App (PWA) Supportを選択しない
  • HTTP リクエストしない(ほぼ無いと思うが)
? Choose features to install
Axiosを選択しない
  • デザインは全部自前
? Use a custom UI framework
None

サーバーの Node フレームワークはあまり詳しくないが、特に今使っているのが無ければ Express か Koa あたりを選んでおけば良いと思う
Vuetify は 1 年くらい使ってるが、かなり完成度高い UI フレームワークと思っているので、入れた
Linter や Prettier や Jest などのテストフレームワークは必須で入れておいた方が良い

実行

package.json を開くと、いくつかスクリプトが設定してあるが、とりあえず以下を実行

yarn dev

マシン環境にもよるが、まあまあ時間はかかる
スクリーンショット 2019-05-27 13.13.34.png

2 回目以降はnode_modules/.cacheの下にbabel-loaderのキャッシュを作ってる影響からか、少し速くなっている
スクリーンショット 2019-05-27 13.13.47.png

でもホットリローディングをサポートしているので、1 回起動すれば遅さは気にならない
nodemonを使って./serverディレクトリ内のファイルを監視してサーバの再起動を行っている

画面が表示されたら HTML ソースを見てみると、server-rendered="true"及び HTML の各要素がそのままレンダリングされていると思う
ちゃんと SSR されていることが分かる
ちなみに SPA モードであれば、JS が実行されてから画面がレンダリングされるので、読み込む JS ファイルのみが指定してあり、HTML はスタイルなどを除けばほとんど空である

また、実行すると.nuxtというディレクトリができており、サーバを立ち上げた時に読み込ませるコンテンツが格納されている
./server/index.jsを見てみると、Nuxt のインスタンスを作成して、Promise を返すnuxt.renderを Express に Middleware としてセットしている

しばらく開発で使うのは、dev スクリプトだけで良さそう

以下の他のスクリプトはある程度 Nuxt でアプリケーション作った後にちゃんと使ってみようと思う

  • build
    • minify して.nuxt/dist/に格納
  • start
    • production モードでサーバを立ち上げる
  • generate
    • Vue を静的コンテンツとしてビルドしてdist/に格納してくれる
    • Netlify や Firebase などでそのままホスティングできる(はず)
    • VuePress 使ったことがあれば、それ

使ってみる

ちょっとだけ使ってみてやったことや思ったことを書いてみようと思う

TypeScript を使う

yarn add -D @nuxt/typescript ts-node

tsconfig.jsonがあればnuxtコマンドで勝手にデフォルト値を書いてくれるらしい

touch tsconfig.json
npx nuxt

先程インストールしたts-nodeは Node で TS をトランスパイルせずに実行できるツールでこれが無いとnpx nuxtが動かない

これで導入は完了

後は、拡張子を.jsから.tsに変えて中身を書き換えていく
以下はserver/index.jsを TS に修正している例

server/index.ts
import NuxtConfiguration from '@nuxt/config'
...
// Import and Set Nuxt.js options
const config: NuxtConfiguration = require('../nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')
...

TS 導入前は上記のconfig.devが any 型だったが、導入後はちゃんと型定義の boolean 型を参照している

package.json も忘れずに修正

package.json
"scripts": {
-    "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
+    "dev": "cross-env NODE_ENV=development nodemon server/index.ts --watch server",
-    "start": "cross-env NODE_ENV=production node server/index.js",
+    "start": "cross-env NODE_ENV=production ts-node server/index.ts",
}

Vue コンポーネントの TS 化についてはドキュメントではvue-property-decoratorの使用を薦めている

yarn add vue-property-decorator

layouts/default.vueを例に修正すると

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'

@Component
class Default extends Vue {
  clipped: boolean = false
  drawer: boolean = false
  fixed: boolean = false
  items: { icon: string; title: string; to: string }[] = [
    {
      icon: 'apps',
      title: 'Welcome',
      to: '/'
    },
    {
      icon: 'bubble_chart',
      title: 'Inspire',
      to: '/inspire'
    }
  ]
  miniVariant: boolean = false
  right: boolean = true
  rightDrawer: boolean = false
  title: string = 'Vuetify.js'
}

export default Default
</script>

ちなみにvue-property-decoratorを使わなくても書ける

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  data(): {
    clipped: boolean
    drawer: boolean
    fixed: boolean
    ...
  } {
    return {
      clipped: false,
      drawer: false,
      fixed: false,
      ...
    }
  }
})
</script>

Vuex を使う

Nuxt をインストールした時にルート直下に store ディレクトリができており、既に Vuex 入ってる感があるが、中身は空である
Nuxt は store ディレクトリが存在すれば Vuex をインポートしてくれて、store をルートインスタンスに流してくれるらしい

なので

  • Vuex を手動で入れる必要がない
  • どのコンポーネントからでもthis.$storeで参照できる

また、モジュールモードとクラシックモードの 2 つのモードがあるが、クラシックモードは廃止予定らしい
肥大化してくるとモジュールごとに State が参照できた方が良いので、モジュールモードで良さそう

Nuxt 側でモジュールモードとクラシックモードのどちらで Vuex インスタンスが作られるかの判定は
store/index.(js|ts)が存在して、Store インスタンスをエクスポートしていれば、クラシックモード
それ以外はモジュールモード

モジュールモードでstore/index.(js|ts)という名前で作った場合、ルートモジュールという扱いで Store インスタンスの直下に State が存在する
store/モジュール名.(js|ts)という名前で作った場合、名前付きモジュールとして Store インスタンスの modules プロパティ配下に State が存在する

それぞれの State を参照する場合

  • ルートモジュールはthis.$store.state.counterで参照できる
  • ルート以外はthis.$store.state.todos.listで参照できる
    • 上記はstore/todos.(js|ts)というファイルを作った場合

ちなみに私は上記のルートモジュールと呼ばれてる Store インスタンスの直下に State を置くのは 1 度もやったことない

以下のファイルを store ディレクトリ配下に追加

store/sample.ts
interface IState {
  counter: number
}

export const state: IState = {
  counter: 123
}

簡単すぎる例だが、ファイルを追加するだけで Store に counter が保持できる

そして以下のように参照する

適当なVue
<script>
export default {
  mounted() {
    console.log(this.$store.state.sample.counter) // 123
  }
}
</script>

また、Vuex のヘルパー関数も使える

適当なVue
<script>
import { mapState } from 'vuex'

export default {
  computed: {
    ...mapState({
      sample: 'sample'
    })
  },
  mounted() {
    console.log(this.sample.counter) // 123
  }
}
</script>

Linter とコードフォーマッターを使う

TS を使っているが、TSLint は非推奨となるらしいので、ESLint の TS パーサーを使う
.eslintrc.jsを見る限り ES(JS)だけの Linter で良ければ、Nuxt をインストールした時点で出来てるので、後は rules や extends でルールを追加するだけで良さそう

package.jsonを見るとeslint-plugin-prettiereslint-config-prettierが依存パッケージとしてインストールされており、.eslintrc.jsの extends に prettier が設定してあるので、ESLint と Prettier の競合は気にしなくて良さそう

TS 用にLinterを使う場合の修正箇所は以下

yarn add -D @typescript-eslint/eslint-plugin
.eslintrc.js
parserOptions: {
-    parser: 'babel-eslint'
+    parser: '@typescript-eslint/parser'
},

-  plugins: ['prettier'],
+  plugins: ['@typescript-eslint', 'prettier'],
package.json
"scripts": {
-    "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
+    "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore .",
}

また、ESLint のルールだと以下のような import は誤検知して、「no-unused-vars」を出してしまう

import NuxtConfiguration from '@nuxt/config'

この場合、以下のように ESLint のルールは握りつぶして、@typescript-eslintの方のルールを有効化する必要があるらしい
こちらの記事に書いてありますが、いくつかこのような誤検知するルールがあるらしいので、その都度同様の対応が必要

.eslintrc.js
rules: {
  'no-unused-vars': 'off',
  '@typescript-eslint/no-unused-vars': 'error'
}

Airbnb の ESLint ルールを入れてみる

yarn add -D eslint-config-airbnb-base
.eslintrc.js
extends: [
  'airbnb-base',
...
]

いい感じに動いているが、Vue とか Vuex は Nuxt に直接依存しており、こちらでpackage.jsonに書く必要が無いため、以下のような Lint エラーが出てる

.../nuxt-boilerplate/pages/index.vue
  63:1  error  'vuex' should be listed in the project's dependencies. Run 'npm i -S vuex' to add it  import/no-extraneous-dependencies

.../nuxt-boilerplate/plugins/vuetify.js
  1:1  error  'vue' should be listed in the project's dependencies. Run 'npm i -S vue' to add it  import/no-extraneous-dependencies

上記は以下のように対応可能

.eslintrc.js
settings: {
  'import/core-modules': ['vue', 'vuex']
},

他にも Nuxt と ESLint で競合するルールはありそう

vue-router を使う

Nuxt ではエントリーポイントとなる JS などで routes を書いてnew VueRouterをやる必要はない
pages ディレクトリ配下にルーターから直接呼ばれるコンポーネントを配置するだけで良い

以下の 2 つを配置

pages/sample/index.vue
<template>
  <span>サンプルインデックス!</span>
</template>
pages/sample/test.vue
<template>
  <span>サンプルテスト!</span>
</template>

それぞれ以下のようにしてルーティングさせる

適当なVue
<nuxt-link to="/sample">Go sample index</nuxt-link>
<nuxt-link to="/sample/test">Go sample test</nuxt-link>

ファイル名がindex.vueであれば、ディレクトリ名が path になり、それ以外のファイル名であれば、ディレクトリ名/ファイル名が path になる
先程の Vuex とルールが似ている

パラメータ付きの動的ルーティングもファイル名もしくはディレクトリ名の先頭にアンダースコアを付けることで実現できるらしい

nuxt-link というコンポーネントは router-link を extends したコンポーネントとなっており、nuxt-link を使わずに今まで通り<router-link>を使って実装することも可能
ただし、nuxt-link を使うと画面表示領域にリンクがあれば、そのリンク先のコンテンツを先読みしてくれるので、遷移が速くなる

試しに、以下のコードを適当な Vue の下の方に配置して、ファーストビューから見えないようにして、スクロールしてリンクを表示させるとindex.vueだけプリフェッチされることが分かる
Chrome DevTool の Network タブで確認すると分かりやすい

適当なVue
<nuxt-link to="/sample">Go sample index</nuxt-link>
<router-link to="/sample/test">Go sample test</router-link>

その他

選択した UI フレームワークでサンプルコードが作られている

私は Vuetify を選んだのだが、割としっかりサンプルコードが Vuetify で作られてて、全部の UI フレームワーク分作られてると思うとちゃんとしてるなって思った

テストのサンプルがほぼ無い

Logo コンポーネントの 1 ケースしかテストのサンプルコードが無い
E2E のサンプルコードは無い

でも最近は Vue のテスト周りのエコシステムが充実してきて、ドキュメントも豊富なので、問題ないと思う

Nuxt のアップデートは簡単にできるのだろうか

Angular みたいなマイグレーション機能はたぶん無いと思うが、Nuxt とか CLI 系のツールを使っているとアップデートが楽にできるとかなりありがたい
簡単にネットで調べた限りは Nuxt を最新にして出たエラーを潰してるっぽい

さいごに

主にツールの導入になってしまい、まだコードはほとんど書いてないのですが、これから ToDo アプリ程度のものは作ってみて、Nuxt を学んでみようかと思います
続きは別記事でまた書こうかなと思います

フロントエンドは全部 1 から作る派なのですが、たまに自動で環境作ってくれる系ツールを使うとそのお手本のような構成に勉強になることがあります
また、今後フロントエンドの環境を作る上で活かせることもあると思うので、試しに使ってみるのもありかなと思いました

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

【日記】lesscコマンドが効かない!→対処したら色々勉強になった

環境:macOS Mojave
MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)

結論:インストール方法を変えたら解決したよ
sudo npm i less --savenpm install -g less
でもPATHが通ってないことが原因だから、パスを指定して実行した方がいいね。


最近、この本でWebアプリ開発を学んでいます。
JavaScriptでのWeb開発 ~ Node.js + Express + MongoDB + ReactでWebアプリを開発しよう ~ その2(iOS対応版)

途中、LESSファイルからCSSファイルを生成するためのlesscコマンドを実行しようとしたところ、ターミナルでエラーが発生しました。

$ lessc less/app.less css/app.css
bash: lessc: command not found

LESSそのものは正しくインストールできているはず。
何がいけないのでしょうかとグーグル先生に尋ねてみたところ、どうにもPATHが通っていないのが原因の模様。PATHって自動で通してくれるものだとばかり思っていました。

lessが入っている場所を確認してみると、ありましたありました。
スクリーンショット 2019-05-30 12.18.07.png
/usr/bin/lessに存在することを無事確認です。

んで、PATHを通すってどうやるのかなと調べていたら、下記の記事を発見。
MacでPATHを通す
なるほど、.bash_profileというファイルに書き込めばいいみたいです。

$ ls -a
.           .gitignore_global   Applications
..          .gitkraken      Desktop
.CFUserTextEncoding .hgignore_global    Documents
.DS_Store       .lesshst        Downloads
.Trash          .local          Google ドライブ
.bash_history       .mongorc.js     Library
.bash_profile       .npm            Movies
.bash_sessions      .pylint.d       Music
.config         .ssh            Pictures
.dbshell        .stCommitMsg        Public
.gitconfig      .viminfo
.gitflow_export     .vscode

ありますね。
PATHの通し方はexport PATH=$PATH:新しく設定するPATHというコマンドを実行すればいいみたいです。
その後sourceコマンドで変更を反映させるのも忘れないように。
最後に、中身を再度確認して反映が通っているかどうか確認します。

$ export PATH=$PATH:/usr/bin/
atsushinoMacBook-Pro:~ anaakikutsushita$ source ~/.bash_profile
atsushinoMacBook-Pro:~ anaakikutsushita$ printenv PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/bin/

/usr/bin/が新たに追加されているのが確認できました。
しかし既に/usr/binが存在していたみたいですね・・・。これはダブっちゃってるみたいだけどいいのかな?

とにもかくにも、PATHが通っている状態にはなったはず。
これでlesscコマンドくんも効いてくれるでしょう。

$ lessc less/app.less css/app.css
bash: lessc: command not found

だめやんけ

さらに調べていると、どうやらインストールの方法からしてちょっと違っていたみたい。
私は技術書通りにsudo npm i less --saveでlessをインストールしていました。
ところがネットの情報を見る限り、npm install -g lessのように-gオプションをつけてインストールしている方法も多く見受けられました。

実際のところこの差がどういう違いになるのかは分かりませんが、とりあえずやってみることにしました。

$ npm install -g less
/usr/local/bin/lessc -> /usr/local/lib/node_modules/less/bin/lessc
+ less@3.9.0
added 60 packages from 123 contributors in 2.057s

あっ、なんだかインストール場所が変わりましたよ的な表示が出ている。
もしかしてこれで正しくPATHが通るようになったりしてないかしら。
もう一度lesscコマンドを試してみましょう。

$ lessc less/app.less css/app.css
atsushinoMacBook-Pro:hoge anaakikutsushita$ cd css
atsushinoMacBook-Pro:css anaakikutsushita$ ls -a
.       ..      app.css

なんとすんなり実行できました。
生成されたCSSの中身を確認しても特に問題なさそうです。

となると、やはり-gオプションをつけてインストールしたことが決め手だったのでしょうか。
このオプションは何を指定するものなのか、ちょっと調べてみましょう。
すぐに次のような記事を見つけることができました。

僕がnpm installに-gをつけないわけ

私が知りたいところだけ抜き出してみると、次のような感じでしょうか。

  • -gをつけるとグローバル領域へのインストールとなり、自動的にPATHが設定される。
  • -gをつけない場合は、実行時にパスを指定する必要が出てくる。
  • 一方、インストール先がグローバル領域じゃなかったらどうなるのと言うと、--saveオプションを付けた場合package.jsonに記載されることになる。

なんとなくわかったようなわからんような感じです。とりあえず今後はなるべく-gオプションは使わない方がいいなと思いました。
package.jsonとかグローバル領域とはなんなんでしょう。こう言うように知らない概念が何個も何個も並列で出てくるとすっごい疲れるんですがいい方法ないものでしょうか。

ざっと説明を読んでみたところ、package.jsonはnpmでインストールしたライブラリの情報をまとめておくファイルのようですね。
確かに、プロジェクトがどのライブラリに依存しているのかと言う情報はどこかにまとめられていないと非常に面倒なので、このファイルのありがたさは理解できます。
そしてこのpackage.jsonファイルが存在していれば、プロジェクトをcloneした時なんかに依存関係にあるライブラリをまとめてインストールしてくれる機能もあると言うことみたいですね。先の記事でポータビリティを議論していたのはそのためでしたか。

じゃあ、なんでもかんでもpackage.jsonに書くようにしちゃった方が平和じゃね?
確かにPATHを通せる利点はあるけど、グローバルにインストールする利点ってなに?
少し調べた限りではグローバルインストールのデメリットばかりが目につきます。
メリットらしいメリットはPATHが自動的に通ること以外には見つけられませんでした。
それどころか、グローバルインストールだとライブラリのバージョンを厳密に指定できないみたいな不都合さもあることがわかりました。

一つ賢くなったので、グローバルにインストールしてしまったlessは一旦アンインストールしておきます。

$ npm uninstall -g less
removed 60 packages in 0.623s
$ lessc less/app.less css/app.css
bash: /usr/local/bin/lessc: No such file or directory

これでよし。
グローバルにインストールしたことで実行できていたlesscも、アンインストールしたことで実行できなくなったことが確認できました。

改めてパス指定してlesscコマンドを実行してみましょう。
lesscがインストールされている場所をあらかじめ確認しておきます。
スクリーンショット 2019-05-30 14.21.07.png
node_modules/less/bin/lesscにありますね。
早速実行してみましょう。

$ node_modules/less/bin/lessc less/app.less css/app.css
$

実行できました。
app.cssの方も正常に作成されました。

思いがけず知らないことがたくさん出てきてお勉強が大変でしたが、この機会に知っておけてよかったです。
今後もグローバルインストールはナシの方向でやっていきましょう。

実はローカルインストールした後でもパスを直接指定せずにラクな方法でコマンドを実行するワザがあるみたいですね。
しかしそこまでは流石に頭が回らなかったため、パス指定の方法が面倒でやってられない時がきた時にまた改めて調べてみたいと思います。

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

nodejs selenium でページ全体をスクショ

https://github.com/nishisuke/selenium-chrome-try

npm init
npm i -S selenium-webdriver

https://github.com/SeleniumHQ/selenium/tree/master/javascript/node/selenium-webdriver#installation
からdriverをインストール

今回はchrome 74

PATHを通す

export PATH=${DOWNLOADED_CHROME_DRIVER}:$PATH
index.js
const { Builder } = require('selenium-webdriver');
const fs = require('fs');

(async () => {

  const driver = await new Builder()
    .forBrowser('chrome')
    .setChromeOptions([
      '--headless',
      '--disable-gpu',
    ])
    .build();

  try {
    await driver.get('http://www.google.com');
    const base64 = await driver.takeScreenshot();
    const buffer = Buffer.from(base64, 'base64');
    fs.writeFileSync('screenshot.jpg', buffer);
  } catch (e) {
    console.log(e)
  } finally {
    await driver.quit();
  }
})();
node index.js

nodeでdom要素のみのスクショはまだできないっぽいです

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

nodejs selenium でページ全体をスクショする

https://github.com/nishisuke/selenium-chrome-try

npm init
npm i -S selenium-webdriver

https://github.com/SeleniumHQ/selenium/tree/master/javascript/node/selenium-webdriver#installation
からdriverをインストール

今回はchrome 74

PATHを通す

export PATH=${DOWNLOADED_CHROME_DRIVER}:$PATH
index.js
const { Builder } = require('selenium-webdriver');
const fs = require('fs');

(async () => {

  const driver = await new Builder()
    .forBrowser('chrome')
    .setChromeOptions([
      '--headless',
      '--disable-gpu',
    ])
    .build();

  try {
    await driver.get('http://www.google.com');
    const base64 = await driver.takeScreenshot();
    const buffer = Buffer.from(base64, 'base64');
    fs.writeFileSync('screenshot.jpg', buffer);
  } catch (e) {
    console.log(e)
  } finally {
    await driver.quit();
  }
})();
node index.js

nodeでdom要素のみのスクショはまだできないっぽいです

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

Web Push Notification のサンプル

まずは、次のコードを実行します。
Web Push Notification Sample for both Server and Client

git clone https://github.com/otiai10/web-push-notification-sample
cd web-push-notification-sample
npm install
node server.js

ブラウザーで http://localhost'8080 にアクセスし次のボタンをクリックします。

Register ServiceWorker
Start Subscribe
Send Push
webpush_may3001.png

次に server.js の trigger: の部分を次のように改造してみます。

server.js
(省略)
  trigger: (req, res) => {
    message_aa = `皆さん こんにちは。 `
    const icon = `img/${Math.floor(Math.random() * 3)}.png`;
    const params = {
      title: "You've got a push-notification!!",
      msg:   message_aa + `Hi, this is message from server. It"s ${new Date().toLocaleString()} now. You can send any message, e.g. notification icons and so`,
      icon:  icon,
    };
    Promise.all(subscribers.map(subscription => {
      return webpush.sendNotification(subscription, JSON.stringify(params), {});
    })).then(res => console.log(res)).catch(err => console.log('ERROR', err));
  },

};
(省略)

再び、サーバーを起動し、ブラウザーでアクセスします。

server.js の変更により、表示されるメッセージが変わります。

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

Node.jsでインフラのテスト、構成管理、オペレーション自動化

とあるプロジェクトで昨年から約1年半、Ansibleでサーバ構築をしていて書きづらいなーと思いながらDocker/Kubenetesとか使いたいけど、それに合わせたシステムの改修をが間に合うわけもなく……

という鬱々とした気持ちを抱えておりました。が、不平不満を言うだけでははじまらないと思い立って、自分の好きなように書けるツールを作成してみました

Submarine - https://gitlab.com/mjusui/submarine

私がAnsibleを書きづらいと感じたのは、別にAnsibleが悪いわけではなく、単にプログラミングパラダイムの問題です
結論から言うと、タイトルどおりNode.jsで書きたかったんです

紹介の前にインフラエンジニア以外の人向けにAnsibleがどういうものなのか、簡単に説明しておきます

Ansibleとは

サーバ構築、構成管理や自動化のためのツール。特徴としては以下のようなものがあります

  • コードは全部YAMLで表現される
  • オブジェクト指向関数の概念はなく、条件分岐とループのみで処理が進んでいく(ただしコードをinclude/importできるので多少の再利用性はある)
  • 変数は基本すべてグローバルアクセス可能で、再代入も自由(一部スコープを制限する機能はある)
  • サーバ構築でよく使う機能(ファイルコピーなど)はmoduleという単位でAnsibleやサードパーティが用意してくれたものを使う
  • moduleを自前で作成することもできる
  • Ansible自体はPythonで開発されており、Pythonの機能や、パッケージを使ってmoduleも開発されている
  • サーバにはsshさえできればよく、専用のエージェントをインストールする必要はない

プログラミング経験があまりないインフラエンジニアにも分かりやすいよう、シンプルに設計されているという印象です

Submarineとは

Node.jsで開発するインフラ管理のFrameworkです。以下のような特徴があります

  • Ansibleは構成管理がメインのツールですが、Submairneはサーバのテストもできます
    • テストの結果に応じて、コマンドを実行する/しないが決定されます
  • Classと継承を使って、コードの再利用性を高めています
  • Node.jsの機能を使えば、変数の再代入を制限したり、スコープを限定することもできます
  • サーバの状態を取得する関数、取得した状態をテストする関数、サーバに変更を加える関数を分離することで、安全で読みやすい(条件分岐の少ない)実装ができます
  • HTTPサーバを起動して、上記で実装した関数をエンドポイントとして公開する機能があります

今回はSubmarineの基本的な機能をサンプルコードをまじえて、紹介していこうと思います

サーバの状態を取得する

まずはサーバから情報を取得します

Node.js
const Submarine = require('Submarine');

const Tutrial = class extends Submarine {
  query(){
    return {
      hostname: 'hostname -s',

      ipv4_addrs: String.raw`
        ip -o -f inet a \
        |awk '{print $4}'
      `
    };
  }
}


const tut=new Tutrial({
  conn: 'bash'
});


tut.current().then((stats)=>{
  console.log(stats);
});

Node.jsが読める人は感覚的にわかるかもしれませんが Submarine というクラスを継承して Tutrial というオリジナルのクラスを定義しています。そして、そのクラスを new でインスタンス化して tut.current() というところで Tutrial クラスに定義した query 関数が実行されます

クラスをインスタンス化するときに { conn: 'bash' } という引数を与えることで Tutrial クラスに定義した query 関数の中のコマンド(hostname -s など)が localhostbash で実行される、ということを指定しています

クラスをインスタンス化するときに例えば { conn: 'ssh', host: <ip address> } といった具合に指定すれば、指定したIPアドレスにsshして、コマンドが実行されます

サーバの状態をテストする

今度は取得した情報をテストします

Node.js
const Submarine = require('Submarine');

const Tutrial = class extends Submarine {
  query(){
    return {
      nonexecutable: String.raw`
        which none \
          2> /dev/null \
          || exit 0
      `,

      executable: String.raw`
        which node \
          2> /dev/null \
          || exit 0
      `,
    };
  }

  test(stats){
    return {
      none_is_not_executable: stats.nonexecutable === '',
      node_is_executable: stats.executable,
    };
  }

}


const tut=new Tutrial({
  conn: 'ssh',
  host: '127.0.0.1'
});


tut.check().then((done)=>{
  console.log(done);
});

query の部分で none というコマンドと node というコマンドのパスをwhichコマンドで引くことができるか確認しています。none というコマンドは、普通は存在しませんから test の中でパスが空 '' であることを検証しています。一方 node というコマンドは、このNode.jsのコードが実行できている以上、どこかに存在しますからwhichコマンドの結果に何らかのパスが含まれていることでしょう。test 関数の戻り値に含まれている2つの値(none_is_not_executablenode_is_executable)は tut.check() という部分で評価され論理積(AND)でTrue/Falseが決定されます。結果は done 変数に格納されています

サーバに変更を加える

サーバの状態を取得して、それをテストしました。テストの結果が問題なければ、何もする必要はありませんが、テストで異常が発見された場合は、それを修正しなければなりません。テストがFalseになった場合にだけ実行されるコマンドを以下のように定義します

Node.js
const Submarine = require('Submarine');

const Tutrial = class extends Submarine {
  query(){
    return {
      file_content: String.raw`
        [ -r /tmp/submarine/hogehoge ] && {
          cat /tmp/submarine/hogehoge
        } || {
          echo 'File not readable' >&2
        }
      `
    };
  }

  test(stats){
    return {
      file_content_is_hogehoge: stats.file_content === 'hogehoge',
    };
  }

  command(props){
    return String.raw`
      mkdir -p /tmp/submarine \
      && echo ${props.msg} > /tmp/submarine/hogehoge
    `;
  }

}


const tut=new Tutrial({
  conn: 'ssh',
  host: '127.0.0.1'
});


tut.correct({
  msg: 'fugafuga'
}).then((done)=>{
  console.log(done);
});

/tmp/submarine/hogehoge というファイルの内容が 'hogehoge' であるか確認し、そうでない場合はファイルが作成され、内容は 'hogehoge' で書き換えられます

done にはコマンドが実行された場合は、実行したコマンドのreturn codeやstdoutの情報が格納され、実行されなかった場合は test 関数のときの結果が格納されます

複数サーバーで実行する

上記の例は1台のサーバに対してコマンドを実行していましたが、複数台のサーバに実行することも可能です

Node.js
const Submarine = require('Submarine');

const Tutrial = class extends Submarine {
  query(files){
    return {
      availables: String.raw`
        df -P \
        |awk '{print $4}' \
        |grep "^[0-9]*$"
      `
    };

  }

  format(stats){
    return {
      available_max: Array.isArray(stats.availables)
        ? stats.availables.map(
            available => available * 1
          ).sort((a ,b)=>{
            return a < b
              ? -1
              : a == b
                ? 0
                : 1;
          }).reverse()[0]
        : stats.availables * 1
    };
  }

}

const Tutrials = Submarine.hosts(
  host => new Tutrial({
    conn: 'ssh',
    host: host
  }),

  server1,
  server2,
  server3,
  server4,
  server5
);

const tut = new Tutrials();


tut.current().then(
  hosts => hosts.map(
    host => host.available_max
  ).reduce((a, b)=>{
    return a + b;
  }) / 1024 / 1024
).then((available_sum)=>{
  console.log(available_sum);
});

server1 から server5 がそれぞれ new Tutrial... でインスタンス化され tut.current() で全台に対して query 関数が評価されます。結果はホスト1台のときと同じフォーマットの結果が、配列で返されます(コードの hosts に格納されている)

このサンプルコードでは、5台のサーバのディスク空き容量を足し合わせています

おわりに

他にも、Ansibleの基本的な機能は置き換えられるようか機能が実装されております

各機能のTutrialを鋭意作成中です

https://gitlab.com/mjusui/submarine/tree/master/doc/en/Tutrial

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

2019年に知っておくべきJava ScriptのフレームワークTOP5

こんにちは、ベイマックスのでかい人形を買おうかどうか迷って半年になるテノヒラです:hand_splayed_tone2:
最近チームでwikiを書く文化が芽生えつつあるんですよ。
わたしもビギナーに向けてトレンドとかをお伝えするものがかければと思って記事を漁っていたら、フレームワークについてこじんまりかついい感じにまとめてある素晴らしい記事を発見したので翻訳してご紹介します!
翻訳する記事はこちら?
Top Javascript frameworks you should know in 2019
世界のトレンドと日本のトレンドはちょっと違ったりするので要チェックです:eyes:

ところどころ意訳をしていますが、もし致命的な翻訳ミスがありましたら編集リクエストしてください:kissing_heart:

はじめに

JavaScriptは世界で一番人気のあるプログラミング言語です。
インターネットでほぼ全てのwebサイトのフォームに使われています。
JavaScriptまたはJavaScriptのフレームワーク、ライブラリ関係なくどこでもJavaScriptを見つけることができます。

JavaScriptはフロントエンドだけでなくサーバーサイドの言語として使われています。
いまではJavaScriptでwebサイトを完全に構築させることができるのです:open_mouth:

下記が2019年で最も人気なフレームワーク5選です。

1) Angular

2) React

3) NodeJS

4) Vue.js

5) Backbone.js

Angular

image.png

Angularは最も人気のあるJavaScriptのフレームワークで、Typescriptのオープンソースです。
最初は2016年9月14日にGoogleがリリースしました。
AngularはMVC (Model-View-Conrtoller)パターンをベースにしています。
これは完璧なフレームワークで、企業のwebアプリケーションを構築するすべてを提供しています。
なのでAngularはwebアプリケーションを構築する上で最も人気のあるJavaScriptなのです。
大企業では大抵採用されています。

React

image.png

Reactはユーザーインターフェイスを創るためのオープンソースのライブラリです。
2013年にFacebookによって開発されました。
Reactはコンポーネントベースで拡張性があり、webインターフェースを構築するのに速いフレームワークです。
Angularと異なっている部分は、Reactはwebインターフェースを構築する機能を提供しています。

最近はReactがwebインターフェースを構築する最も人気があるJavaScriptライブラリのひとつとなりました。
ただ、Reactは最も手のかかるJavaScriptのライブラリです。
需要高いのでやれるようになれば思わず笑みが溢れるよう給与がもらえます:smirk:

Node JS

image.png

Nodejs、Node.jsもしくはNodeはオープンソースで、Google ChromeのJavaScript V8 Engineに基づいたサーバーサイドのJavaScriptのフレームワークです。
主に速い構築、拡張性、サーバーサイドアプリケーションをベースとした信頼性の高いネットワークを主に設計しています。
Indeed.comには現在、8,905つの求人があります。
Node開発者の平均的な給与はだいたい 10万5千ドル(約1,148万円)です。

Vue.js

image.png

Vueもしくはvue.jsはユーザーインターフェースを構築するための革新的なフレームワークです。
ライブラリのコアは、表示のレイヤーだけに焦点を合わせている、簡単に習得できる、他のライブラリもしくは既存のプロジェクトとの統合といった部分です。
違う視点からみると、Vueはモダンなツールとサポートしているライブラリとを一緒に使った時に洗礼されたシングルページアプリケーションにすることができます。
webページの表示に焦点を当てたVueもフロントエンド界隈では人気のあるフレームワークとなります。

Indeed.comには現在、1,316つの求人があります。
平均的な給与はおよそ10万ドル(約1,093万円)もしくはそれ以上です。

Backbone.js

image.png

BackboneもしくはBackbone.jsはRESTful JSONインターフェースのフレームワークで、model–view–presenter (MVP)アプリケーションデザインパターンをベースとしているものです。

Backboneは2010年にJeremy Ashkenasによってつくられ、7年後には最も人気のあるフレームワークのひとつとなりました。

Indeed.comには現在、5,135つの求人があります。
backboneフロントエンドとbackboneフルスタックエンジニアの平均的な給与はそれぞれおよそ10万2千ドル(約1,115万円)と10万1千ドル(約1,104万円)です。

React Vs Angular

ReactとAngularはweb開発者によって最も討論されることです。
これらを学習したプログラマーと開発者にとってどちらを学ぶべきなのかは悩むところです。
webサイトを開発する人にとってはどちらを使うべきかを悩みます。
どちらを採用するかを決定するのはいつも課題となります。

Angularは完全なweb applicationの構築する機能を提供しており、Reactはインターフェースだけを構築するのに使われているライブラリです。
アーキテクチャーの概念によると、Angularはモデルと表示、制御の機能を処理しますが、Reactは表示のみを処理します。
AngularはTypescriptをベースとしていますが、ReactはJavascript上で動きます。
ReactはFacebookによって開発されましたが、AngularはGoogleによって開発されています。
AngularのIonicはモバイルアプリケーションの構築に使われますが、
Reactはモバイルアプリケーション開発のためにReact Nativeを提供します。

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