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

【Node.js】bundleサイズは大事って話

$
0
0

サービスの動作が遅かった

最近,アーチャー伝説ってスマホアプリ専用の【マルチ募集掲示板サービス】をリリースしました.

技術としては,Node.js/React/TypeScript/Express/Webpackを使用しました.このサービス,募集スレッドのリアルタイム更新をアピールしてるんですが,その肝心のbundle.jsが動き出すのが遅いんですよね.

そこでSSR(Server Side Rendering)について調べてたらこんなサイトが出てきて,めっちゃわかりやすいやんけ!と.要するにNode.jsってすごいし早いけど,やっぱSSRしない方が動作は早いよねと.でもSEOの観点とUX考慮するとまあSSRした方がいいよねと.

そこで重要になるのがjsの読み込み速度.今回Webpackを使ってるのでここをなんとかせねば!Webpackってjs全部ひとまとめにしてくれてめちゃくちゃ便利な反面,そのbundleされたjsファイルに対して何もしなかったらかなり重いんです.

bundle.jsを小さくするために!

今回ReactのMaterial-UI使ってたり,Express.js使ってたりと,小さく出来そうなとこはいろいろありそうでした.サイズ小さくするために,ちょっとめんどくさい書き方になったりしますが,これはユーザビリティ考えると致し方ありませんね.ちなみに下の画像がなーんにも手を加えずbundleしてた時のサイズ.1.29Mbもあります.(BundleAnalyzerPluginって神様)ちなみにこれjs読み終わるのに10秒くらいかかってました....
スクリーンショット 2020-04-17 21.51.58.png
で,この中でも,Material-UIが997Kb...これだけでbundle.js全体の8割弱占めてる...Material-UI使う時は必ず注意すべきですね
スクリーンショット 2020-04-17 21.52.10.png
ささ,bundleサイズ小さくしていきましょうか.

Express.js

importcompressionfrom'compression';app.use(compression());

これだけでbundle.jsをgzipで圧縮してくれるんです.便利すぎて笑

Material-UI

//GoodimportAppBarfrom'@material-ui/core/AppBar';//Badimport{AppBar}from'@material-ui/core';

上のように書き換えます.それだけです.僕の場合は多い時は1コンポーネントに10個ほどモジュールをimportしてたので,かなりめんどくさい...下の書き方だったら一行でほとんどimportできますから.
で,なぜ下はダメなのか.これ直接node_modulesディレクトリから探せば一目瞭然ですが,下の場合だと@material-ui/core全部インストールしてきてそっからAppBarを探すんですよね.そりゃ容量食いますね.

CSS

constsheets=newServerStyleSheets();renderToString(sheets.collect(React.createElement(component)));constcss=(sheets.toString()).replace(/\s+/g,"");

僕の場合はMaterial-UIで使われるcssもSSRでテンプレートエンジン(pug)に渡す必要があったので,cssを文字列で渡すんですよね.で,cssとかjsの読み込みに空白とか改行,コメントが入ってたらそれまた遅くなります.なのでここではスペースを全て削除することでcssを圧縮しました.

bundleサイズのビフォーアフター

bundleサイズ438Kb!
スクリーンショット 2020-04-17 21.52.41.png
Material-UIも258Kbに!
スクリーンショット 2020-04-17 21.52.51.png

結果,800Kb以上圧縮できましたね!6.5割程削減できたのかな?

【マルチ募集掲示板サービス】の読み込みもだいぶ早くなりました!ストレスは感じないですね!Lighthouseで測定したら,Performanceが60点台から96点になりました!!
スクリーンショット 2020-04-17 22.35.34.png
アクセシビリティも改善したいのですが,サードパーティコードの影響を削減ってよくわからなくてわからないです....このサイトによるとNode.jsってサーバのメインループをブロックするらしくて,そのことかなと思うのですが...誰か教えていただけると幸いです!
スクリーンショット 2020-04-17 22.39.16.png

まとめ

Webpackとかでbundleしたり,いろんなモジュール使うならサイズ小さくしようってことです!


Node.jsについて調査とまとめ(自分用。編集中)

$
0
0

このたび、仕事(Not 個人の開発)でNode.jsを利用して開発することになったので、その調査を行う。
ポイントとしては「開発者は自分だけではない」ということか。自分だけなら理想を求めて翻訳資料が少なかったりまだ不安定だったりするライブラリも利用できるが、組織でとなるとそうはいかない。

前提

今回はフロントエンド、バックエンドを分離して作成することを前提としている。
Node.jsはそのバックエンド側(APIサーバ)で利用する

フレームワーク

Node.jsを利用するとして、フレームワークの利用を検討する。
そのまま利用しても構わないが、少しでも手間は省きたい。
また、自分だけでなく多様な人が利用するため、フレームワークの調査市やすさも重要である。
(自分だけなら英語でも読むけど……ね。調べて教える?時間があればやるよ。そんな時間ができることは一生無いけどな!)

https://www.wantedly.com/companies/company_3239475/post_articles/179467

React+ReduxアプリにNode.js(express)とJWTで認証・認可周りの処理を実装する①API側

$
0
0

はじめに

React+ReduxアプリケーションにNode.js(express)でAPIを作り、jsonwebtoken(JWT)で認証・認可周りの処理を実装してみました。今回はAPI側の実装の内容を書いていきます。
ソースコードはGitHubにありますので、参考にしてください。

使ったもの

  • Node(v12.15.0)
  • express(v4.17.1)
  • jsonwebtoken(v8.5.1)
  • body-parser(v1.19.1)
  • nodemon(v2.0.3)
  • REST Client(VSCodeの拡張機能)

ディレクトリ構成

root/
 ├ api/
 │  ├ login.js
 │  └ users.js
 ├ config/
 │  └ jwt.config.js
 ├ middlewares/
 │  └ verifyToken.js
 ├ app.js
 └ request.rest

作るもの

[POST] /api/v1/login

ユーザーIDとパスワードを付与してリクエストすれば、認証が通ればjwtでトークンを返す

[GET] /api/v1/users

トークンをヘッダーに付与してリクエストすれば、ユーザー情報を返す

プロジェクト作成

プロジェクト作成して、必要なパッケージをインストールします。

npm init -y
npm install --save express jsonwebtoken body-parser nodemon

app.jsを作成

とりあえずexpressを読み込んで、appで初期化して、app.listenで起動する普通の流れです。
ルーティングは別ファイルにするので、express.Routerを初期化してリターンします。

app.js
constexpress=require("express");constbodyParser=require("body-parser");constapp=express();app.use(bodyParser.urlencoded({extended:true}));app.use(bodyParser.json());app.use("/api/v1",(()=>{constrouter=express.Router();router.use("/login",require("./api/login.js"));router.use("/users",require("./api/users.js"));returnrouter;})());app.listen(5000,function(){console.log("Example app listening on port 5000!");});

login.jsを作成

認証は本来はデータベースから行うが今回は割愛します。

login.js
constrouter=require("express").Router();constjwt=require("jsonwebtoken");constconfig=require("../config/jwt.config");// POST /api/v1/loginrouter.post("/",(req,res)=>{// 本来はデータベースからユーザーIDとパスワードを認証するが割愛if(req.body.userId=="00001"&&req.body.passWord=="qwerty"){constpayload={userId:req.body.userId,};consttoken=jwt.sign(payload,config.jwt.secret,config.jwt.options);res.json({isSuccess:true,token:token,});}else{res.json({isSuccess:false,message:"ユーザーIDまたはパスワードが違います。",});}});module.exports=router;

jwt.config.jsに設定を記載する。

jwt.config.js
module.exports={jwt:{secret:"cdfa54a89e0fbd4596213bf175336cfe05a78a73383f6dab39b8d374e7dceba53630546ff4c998b7bab9e53eaab2519a48e970b094315cf7472b811e45c1ea29",options:{algorithm:"HS256",expiresIn:"20m",},},};

シークレットキーはコマンドプロンプトで以下入力で取得したものです。

node
require("crypto").randomBytes(64).toString("hex")

ログインリクエストを試してみる

nodemonをインストールしてあれば以下で起動できます。

nodemonapp.js

今回リクエストにはVSCodeの拡張機能の[REST Client]を使います。request.restファイルに以下のように記述すれば、
上部に[Send Request]が表示されるのでクリックすれば、リクエストできます。

request.rest
POSThttp://localhost:5000/api/v1/loginContent-Type:application/json{"userId":"00001","passWord":"qwerty"}

認証に成功すれば以下レスポンスがあると思います。これでトークンが取得できましたで、ユーザー情報を取得します。

{"isSuccess":true,"token":"xxxxxxxxxxxxxxxxxxxx"}

verifyToken.jsを作成

まずトークンを認証するミドルウェアを作成します。
リクエストヘッダーには以下のようにトークンを付与します。

Authorization: Bearer xxxxxxxxxxxxxxxxxxxx

ヘッダーからトークンを取得して認証処理を行います。
認証が成功したら、req.decodedにpayloadで設定した値が格納される。

verifyToken.js
constjwt=require("jsonwebtoken");constconfig=require("../config/jwt.config");functionverifyToken(req,res,next){// splitで半角スペースで分割して後ろ側がTokenになるconstauthHeader=req.headers.authorization;consttoken=authHeader&&authHeader.split("")[1];if(token){jwt.verify(token,config.jwt.secret,function(error,decoded){if(error){returnres.status(403).send({isSuccess:false,message:"トークンの認証に失敗しました。",});}else{req.decoded=decoded;next();}});}else{returnres.status(401).send({isSuccess:false,message:"トークンがありません。",});}}module.exports=verifyToken;

users.jsを作成

こちらもは本来はデータベースから情報を取得するが今回は割愛します。
先ほど作成した[verifyToken]を付与すれば、認証が行われ[req.decoded]にユーザーIDが格納されるので、その値をもとにユーザー情報を取得してレスポンスを返します。

login.js
constrouter=require("express").Router();constverifyToken=require("../middlewares/verifyToken");// GET /api/v1/usersrouter.get("/",verifyToken,(req,res)=>{// 本来はデータベースからユーザーIDを元にデータを取得するが割愛letresults={};if(req.decoded.userId=="00001"){results={userId:req.decoded.userId,name:"Tom",};}res.json(results);});module.exports=router;

ユーザー情報取得リクエストを試してみる

先ほどログインした時のトークンをリクエストヘッダーに付与して、リクエストする。

request.rest
GEThttp://localhost:5000/api/v1/usersAuthorization:Bearerxxxxxxxxxxxxxxxxxxxx

認証に成功すれば以下のようにレスポンスがあるはずです。

{"userId":"00001","name":"Tom"}

まとめ

これで、API側の実装は完了です。本来はデータベースが必須になるのでそのうちMySqlかMongoDBあたりで実装するかもしれません。
とりあえず次回はフロント側の実装をしていきたいと思います。何かまずいところあればコメントお願いします。

参考にしたもの

Introduction to JSON Web Tokens
JWT Authentication Tutorial - Node.js

laravel-mix yarn run dev で vue-template-compiler ライブラリが依存してた

$
0
0

環境

  • PHP 7.4.4
  • Laravel 7.5.1
  • Node 12.16.1
  • yarn 1.22.0
package.json
{"private":true,"scripts":{"dev":"yarn run development","development":"cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js","watch":"yarn run development -- --watch","watch-poll":"yarn run watch -- --watch-poll","hot":"cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js","prod":"yarn run production","production":"cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"},"devDependencies":{"axios":"^0.19","cross-env":"^7.0","laravel-mix":"^5.0.1","lodash":"^4.17.13","resolve-url-loader":"^3.1.0","sass":"^1.15.2","sass-loader":"^8.0.0"}}

問題

$yarn run development
$cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress--hide-modules--config=node_modules/laravel-mix/setup/webpack.config.js
    Additional dependencies must be installed. This will only take a moment.

    Running: npm install vue-template-compiler --save-dev --production=false

/bin/sh: npm: not found

私の環境ではnpmコマンドは入ってないのでエラーとなりました。
laravel-mixvue-template-compilerに依存してるのか...🤔
なんだか気持ち悪いがそれならそれで初めから同梱して欲しい。。

$yarn add vue-template-compiler --dev--production=false

vue-template-compiler入れて解決。
まぁないんだからそうですよね。

$yarn run dev
yarn run v1.22.0
$yarn run development
$cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress--hide-modules--config=node_modules/laravel-mix/setup/webpack.config.js
98% after emitting SizeLimitsPlugin

 DONE  Compiled successfully in 6532ms                                                                                       5:09:41 PM

       Asset     Size   Chunks             Chunk Names
/css/app.css  0 bytes  /js/app  [emitted]  /js/app
  /js/app.js  592 KiB  /js/app  [emitted]  /js/app
Done in 18.62s

参考

[firestore] Error: 7 PERMISSION_DENIED: Missing or insufficient permissions

$
0
0

前提

GOOGLE_APPLICATION_CREDENTIALSを利用してサービスアカウントを設定している。

問題

firestoreにwriteする際に、タイトルエラーが出て書き込めなかった。

firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth != null;
    }
  }
}

firestore.rulesを見ても、特に問題は見当たらない。

解決

以下のコードが原因

import{initializeApp}from'firebase-admin'initializeApp({projectId:"YOUR_PROJECTID"})

GOOGLE_APPLICATION_CREDENTIALSを利用して設定している場合、initializeAppで余計な設定は不要

import{initializeApp}from'firebase-admin'initializeApp()

これでよし。

Vue CLI導入メモ

$
0
0

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

1. Homebrewのインストール

ターミナルに下記のコマンドを打ち込んでMacOS用のパッケージマネージャー「Homebrew」をインストールする。

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

2. nodebrewを使ってNode.jsをインストールする

下記のコマンドでNode.jsのバージョン管理ツール「nodebrew」をインストール

nodebrewのインストール
$brew install nodebrew

nodebrewを使って安定版のNode.jsをインストールし、有効化

Node.jsのインストール
#安定版のインストール
$nodebrew install-binary stable
#インストール確認
$nodebrew list

v16.12.2

current: none

#安定版を有効化
$nodebrew use stable

3.環境変数の設定

nodebrewのsetupでパスを確認

環境変数設定
#nodebrewのセットアップ
$nodebrew setup

Fetching nodebrew...
Installed nodebrew in $HOME/.nodebrew

========================================
Export a path to nodebrew:

export PATH=$HOME/.nodebrew/current/bin:$PATH========================================

ターミナルでzshを使っている場合、「~/.zshrc」に上記のexport PATH=$HOME/.nodebrew/current/bin:$PATHを追記して保存

最後にターミナルで下記を実行

環境変数設定の適用
$source ~/.zshrc

2.VueCLIプロジェクトの作成

1.VueCLIのインストール

VueCLIのインストール
$npm install-g @vue/cli

2.VueCLI新規プロジェクトの作成

任意のプロジェクトフォルダに移動して下記を実行

新規VueCLIプロジェクト作成
$vue create プロジェクト名

3.VScodeとGithub連携

1.GithubのリモートリポジトリにSSHキーを追加

ここの記事を参照

2.VScodeとの連携

VScodeでプロジェクトフォルダを開き、ターミナルウィンドウを開いて下記のコマンドを実行

リモートリポジトリの設定
$git remote set-url origin SSH版リポジトリURL

4.その他

リモートからcloneするときはnode_moduleを下記のコマンドでインストールする

プロジェクトのセットアップ
$npm install

node.jsでsoket.ioを使用して簡単なチャットを作成してみた

$
0
0

まずはsocket.ioモジュールを追加しましょう。

https://www.npmjs.com/package/socket.io

npm i socket.io

で追加します。

1.まずはサーバーサイドのコードを書いてみます
説明はコード内のコメントを読んでいただけるとありがたいです。

SoketIoServerTest.js
//httpモジュール読み込みvarhttp=require('http');// Socket.IOモジュール読み込みvarsocketio=require('socket.io');// ファイル入出力モジュール読み込みvarfs=require('fs');//パスのモジュール読み込みconstpath=require('path');//8080番ポートでHTTPサーバーを立てるvarserver=http.createServer(function(req,res){//リクエストされたファイルパスを取り出すvarfilePath="."+req.url;console.log(filePath);//ファイルパスのファイル名がなければindex.htmlif(filePath=='./'){filePath='./index.html'}//拡張子名を取り出すvarextname=String(path.extname(filePath)).toLowerCase();console.log('extenname:'+extname); //拡張子に対応するコンテンツ表varmimeTypes={'.html':'text/html','.js':'text/javascript','.css':'text/css','.json':'application/json','.png':'image/png','.jpg':'image/jpg','.gif':'image/gif','.wav':'audio/wav','.mp4':'video/mp4','.woff':'application/font-woff','.ttf':'application/font-ttf','.eot':'application/vnd.ms-fontobject','.otf':'application/font-otf','.svg':'application/image/svg+xml','.ico':'image/x-icon'}; //MIMEがない場合はデフォルトを使用 varcontentType=mimeTypes[extname]||'application/octet-stream'; //リクエストされたファイルパスを読み込みfs.readFile(filePath,function(error,content){//エラー処理if(error){console.log('ERROR!!'+error.code);//リクエストしたファイルが存在しない場合if(error.code==='ENOENT'){//ファイルパスの先にファイルがないエラーres.writeHead(404);res.write('Sorry, check with the site admin for error: '+error.code+' ..\n404');res.end();}else{//CGIエラーres.writeHead(500);res.write('Sorry, check with the site admin for error: '+error.code+' ..\n500');res.end();}}else{//エラーがない場合、正常処理res.writeHead(200,{'Content-Type':contentType});res.end(content,'utf-8');}});}).listen(8080);//サーバーをソケットに紐づけるvario=socketio.listen(server);//接続確立後の通信処理部分を定義io.sockets.on('connection',function(socket){varname;console.log('コネクション:'+socket);//クライアントからサーバーへメッセーゾ送信ハンドラ(自分を含む全員に送る)socket.on('c2s_message',function(data){//サーバーからクライアントへメッセージ送り返しio.sockets.emit('s2c_message',{value:data.value});console.log('通常メッセージ:'+data.value);});//サーバーからクライアントへメッセージ送信ハンドラ(自分以外の全員に送る)socket.on('c2s_broadcast',function(data){//サーバーからクライアントへメッセージ送り返しsocket.broadcast.emit('s2c_message',{value:data.value});console.log('ブロードキャストメッセージ:'+data.value);});socket.on('c2s_personal',function(data){varid=socket.id;name=data.value;varpersonalMessage='あなたは、'+name+'さんとして入室しました。';console.log('ゆーざーID:'+id);io.to(id).emit('s2c_message',{value:personalMessage});});});

2.次はクライアント側のjavascriptを書いてみます
こちらも説明はコード内のコメントを読んでいただけると幸いです。
ちなみにjQueryを使用しています。index.htmlの方で読み込んでおいてくださいね。

js/clienttest.js
//空のioSocket用意varioSocket={on:function(){}};functionconnect(){if(!ioSocket.connected){//サーバーへ接続ioSocket=io.connect('http://'+window.location.host);console.log('URL:'+window.location.host);console.log('サーバー接続!!');}else{//サーバーへ再接続ioSocket.connect();console.log('サーバー再接続!!')}}connect();//サーバーからの受け取り処理ioSocket.on('connect',function(){});ioSocket.on('disconnect',function(){});varisEnter=false;varuserName;//サーバーからの送り返しioSocket.on('s2c_message',function(data){appendMessage(data.value)});//画面にメッセージを描画functionappendMessage(text){$('#messageView').append('<div>'+text+'</div><br/>');console.log("メッセージ:"+text);}//自分を含む全員宛てにメッセージを送信$('#sendMessageBtn').on('click',function(){//メッセージの内容を取得し、フォームをレフレッシュvarmessage=$('#messageForm').val();$('#messageForm').val('');//入室処理if(isEnter){message='['+userName+'] '+message;//クライアントからサーバーへ送信ioSocket.emit('c2s_message',{value:message});}else{userName=message;varentryMessage=userName+'さんが入室しました。';ioSocket.emit('c2s_broadcast',{value:entryMessage});ioSocket.emit('c2s_personal',{value:userName});changeLabel();}});functionchangeLabel(){$('input').attr('placeholder','message In');$('button').text('送信');isEnter=true;}$(window).on("beforeunload",function(){vargetoutMessage=userName+'さんが退室しました。';ioSocket.emit('c2s_broadcast',{value:getoutMessage});ioSocket.disconnect();});

3.簡単なhtmlを作成します。

index.html
<!DOCTYPE html><html><head><metacharset="UTF-8"><script type="text/javascript"src="./lib/jquery-3.4.1.min.js"></script><script type="text/javascript"src="node_modules/socket.io-client/dist/socket.io.js"></script></head><body><style>#messageForm{margin-top:15px;}</style><divclass="container"><divclass="row"><divclass="col-md-6 col-md-offset-3"><divclass="form-group"><inputtype="text"class="form-control"id="messageForm"placeholder="name In"/><buttonclass="btn btn-primary"id="sendMessageBtn">入室</button></div><divid="messageView"></div></div></div></div><script type="text/javascript"src="js/clienttest.js"></script></body></html>

では、ターミナルを開いて
node サーバーファイル名

で起動してからブラウザで
http://localhost:8080/
にアクセスしてみましょう。

このような動作になるかと思います。

スクリーンショット 2020-04-19 16.00.01.png

ブラウザの別タグでログインしてメッセージを送信してみます。

スクリーンショット 2020-04-19 16.05.57.png

メッセージが来ました!!さらに同一Lan内であればスマートフォンでチャットも出来ます。送信してみます。

Screenshot_20200419-160842.png

届きました!!

スクリーンショット 2020-04-19 16.13.27.png

以上で終わりですが動きがあるのは楽しいですね。

余談ですが
JavaとjavascriptでWebSocketを試してみよう!
https://qiita.com/keisuke1112/items/5c989eb8f0e868a25435

私の別記事でjavaとjavascriptでwebsocketを使用してみたのですが、node.jsの方が手軽に書けるような気がします。

新人に負けない本棚管理サイト その1

$
0
0

目次

新人に負けない本棚管理サイト その1(プロローグ)
新人に負けない本棚管理サイト その2(環境構築編)

イントロ

4月に新入社員がやってきました。
私もほんの1年前まで同じ立場だったのに、いつのまにか若葉マークが剥がされたり先輩が昼ごはんをおごってくれなくなったり、いろんな変化がありました。
なんとなく「若葉マークは時間とともに次第に見えなくなっていき、3月末に別れを告げ、4月を迎えると何もなかったかのように消えてる」みたいなメルヘン展開を想像していたのですが、
全然そんなことなく通常の業務からバッサリ「新人扱い」だけが抜け落ちた状態になります。
2年目を迎えた人にしかわからない感覚なのかなぁと思います。

今年の新人研修

技術研修で「本棚管理のWebサイトをつくる」課題が出たそうです。
本棚管理やToDoリストってWeb開発やアプリ開発の登竜門ですよね。
機能や見た目がシンプルな上にデータベースの処理を網羅できるので、非常に取り組みやすいんです。

先週から自宅勤務になった私も、土日の暇を持て余していますので新人のフリをして(?)本棚管理サイトをつくってみたいと思います。
それに当たり、少し前から触っているNext.jsというものを使って制作していきます。

開発環境

OS          : Windows10
サーバーサイド    : Node.js(自宅のRaspberryPiに設置)
UIライブラリ     : React.js
Reactフレームワーク : Next.js
スタイリング     : react-bootstrap(Reactの中でbootstrapを使えるようにするライブラリ)
データベース     : MariaDB(自宅のRaspberryPiに設置)
おやつ        : オーザックのり塩(ちっちゃいときから好き)

作るもの

基本機能

トップページ

持っている書籍の一覧を表示します。

本の登録ページ

入力項目
 【必須】書籍名、購入日
 【任意】著者名、価格、購入した場所(〇〇書店、Amazonなど)
を入力してボタンクリックで登録します。

本の編集ページ

登録内容を編集したり、読了したらチェックをつけたりします。

ちょっと背伸び

会員情報、ログイン処理

ここまでの段階では本の一覧ページがトップページですが、ログインページをつくってこれをトップページにしてみます。
そしてログイン認証し、セッションを使って会員情報を持ち回れるようにします。
これにより、自分用だったものがみんなで共用できるものに変わります。

貸し借り機能、貸し出しランキング

会社にある書籍の管理を想定しているので、貸し借りを管理できたほうがいいと思います。
学校の図書室みたいに紙ベースでやるのはもう時代遅れです!!
貸し出しの履歴を残すことで「あいつまだ返してねぇぞ!!押しかけるぞ!!」と、一揆を起こす確認することができます。

あと、うちの会社の人たちは何に興味があるんだろうっていうのがひと目で分かればとても便利です。
そのためのランキング機能です。
開発者はシャイな人が多いので、あまり自分から話し出そうとしません...。

さらなる高みへ

書籍名を非同期で検索

で、でたーw非同期検索奴www
ゴホン
私がWeb開発を学んでいく中で初めてつまずいたポイントです。
書籍名によってはカッコとか空白とか、どうしても書きづらいときがあると思うんですよね。
そういうときにISBNや著者名で検索して選んで入力できたら便利です。
幸いにもGoogle大先生が書籍検索APIを公開してくれているので、使ってみたいです。

次回

環境構築をします。
ただインストールするだけなのでココみてねって外部サイトへ誘うのは、初めてやる人にとっては案外ハードルが高かったりするんです。
なので1つ1つ丁寧に書いていくつもりです。

べ、別に、文字数稼ぎのためにやってるんじゃないんだからね!
勘違いしないでよね!!
ふんっ!!


create-guten-blockとwp-envで簡単にGutenbergブロック開発を行う

$
0
0

独自にGutenbergブロックを開発したい場合、一般的には、(1)Gutenbergブロックの開発プロジェクトと、(2)WordPressのテスト環境が必要になります。それぞれ全てをスクラッチで構築しても良いのですが、今回はcreate-guten-blockwp-envを使って、簡単に環境を構築する方法を紹介します。
なお、この手順は2020/4/19時点の内容なので、将来はもっと便利な方法が出てきているかもしれません。

今回採用した方法と、他の方法

環境構築の方法に正解はなく、色々な方法で環境を構築できます。
参考までに、他の選択肢と、今回採用した方法をのせておきます。(ここにある方法が全てでもありません)

(1)Gutenbergブロックの開発プロジェクト作成方法

  • 全てを自分で作成する
    ソースファイルやビルド用の環境など全てを自分で構築する方法です。
    自由な構成ができる反面、環境作成には手間がかかります。

  • @wordpress/scriptsを使う方法
    https://developer.wordpress.org/block-editor/packages/packages-scripts/
    @wordpress/scriptsを使えばシンプルなGutenbergブロックの環境を作成できます。
    PHPファイルやSass環境の構築などは自分で行う必要があります。

  • create-guten-blockを使う方法(今回採用した方法
    https://github.com/ahmadawais/create-guten-block
    create-guten-blockを使うとプラグインのテンプレートやSass環境などが自動で作成されます。
    基本的にはsrcフォルダ配下のコードを編集していく形になります。

(2)WordPressのテスト環境の作成方法

  • レンタルサーバなどに独自にサーバを立てる
    一般に公開するときと同じように、テスト用の環境をレンタルサーバなどに用意する方法です。
    修正したプラグインを都度アップロードする必要があったり、複数環境を作成しづらい…など色々と手間がかかるのでおすすめしません。

  • docker-composeでWordPress環境を作成する方法
    docker-compose.ymlにWordPressやdbコンテナの定義を書いて環境を作成する方法です。
    この方法を使っている人も多いと思いますが、docker-compose.ymlの編集などが少し面倒です。

  • wp-envを使う方法(今回採用した方法
    https://developer.wordpress.org/block-editor/packages/packages-env/
    wp-envを使うとプラグインやテーマのフォルダでwp-env startとするだけで、WordPressやdb環境が自動で作成され、プラグインやテーマが自動でバインドされた状態になります。
    内部ではdocker-composeが使われていますが、利用する側はdocker-composeを意識せずに使えます。
    利用するWordPressのバージョンやポート番号など、細かい設定は.wp-env.jsonを作っておけばOKです。

環境準備

事前に次の環境が必要になります。

  • Windows 10 Pro(macOSやLinuxなどでも可能なはず)
  • Docker Desktop(Docker Composeも自動で入ります)
  • Node.js(Docker環境ではなく、Windows側にNode.jsのインストールが必要です)
  • wp-envのインストール
    コマンドプロンプトでnpm install @wordpress/env -g
    グローバル環境にインストールしていますが-gをつけずにローカルにインストールも可能。
    ローカルにインストールした場合は、wp-envnpx wp-envと読み替えてください。

具体的な手順

今回はカスタムブロックmy-custom-blockS:\my-custom-blockフォルダで開発&テストする前提の手順になります。

create-guten-blockで開発プロジェクト作成

コマンドプロンプトを開いて以下のコマンドを実行します。(#で始まる部分はコメントです)

# フォルダに移動cd /d s:\# my-custom-blockプロジェクトの作成
npx create-guten-block my-custom-block

# プロジェクトフォルダに移動してsrcフォルダを監視(srcフォルダの内容が変われば自動でビルドされる)# 終了したい場合はCtrl+Ccd my-custom-block
npm start

wp-envを使ってテストする方法

もう一つ別でコマンドプロンプトを開いて以下のコマンドでWordPress環境を作成します。
ポイントとしては、プラグイン用のフォルダS:\my-custom-blockでwp-envコマンドを実行することです。

# プラグインのフォルダに移動cd /d s:\my-custom-block

# wp-envでWordPressの実行# @wordpress/envをローカルにインストールした場合はnpx wp-envで実行
wp-env start

WordPressが起動したら、http://localhost:8888/でWordPressにアクセスできます。
WordPressのHello world!ページが表示されると思うので、管理画面に入りたい場合はLoginから、User:admin、Password:passwordでログインしてください。最初は英語版になっていますが、WordPressの設定から日本語に変更できます。
なお、この時点でmy-custom-blockプラグインは有効になっています。

WordPressを終了させたい場合はwp-env stopでOKです。

wp-envの設定

wp-env startを実行するフォルダに.wp-env.jsonを入れておくと、その設定に従ってwp-envの環境が作成されます。今回の例ではS:\my-custom-block\.wp-env.jsonになります。
必須ではありませんが、内容も簡単なので、次のような感じで.wp-env.jsonを作っておくことをおすすめします。

{"core":null,"plugins":["."],"port":10088,"testsPort":10089}

これで、最新のWordPress("core":null)で、ポート番号に10088番が使われるようになります。

Node.js基礎:文字列処理

$
0
0

はじめに

文字列の処理はもっとも使う処理です。Node.jsの文字列処理をまとめてみます。

基本:シングル、ダブルクォーテーションで囲んで定義

sample.js
// 単一行conststr1="abc";// 複数行conststr2=`あいうえお
かきくけこ
はひふひほ`// 複数行: +で連結conststr3="abc"+"\n"+"def";// シングルクォーテーションで囲んで定義conststr4='あいうえお'+'\n'+'かきくけこ';

文字列処理メソッド

説明ページ:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String

  • String.prototype.startsWith()
  • String.prototype.endsWith()
  • String.prototype.split()
  • String.prototype.replace()
  • String.prototype.indexOf()
  • String.prototype.toLowerCase()
  • String.prototype.toUpperCase()
  • String.prototype.trim()
  • String.prototype.charAt()
  • String.fromCharCode()

など。用意されているメソッドがたくさんあります。

Bufferクラス

Node.jsの中ではBufferを使って文字列処理のメソッドもたくさんあります。
説明ページ:https://nodejs.org/api/buffer.html

例:

node.js
constbuf=Buffer.from('こんにちは!','utf8');console.log(buf.toString('hex'));// e38193e38293e381abe381a1e381afefbc81console.log(buf.toString('base64'));// 44GT44KT44Gr44Gh44Gv77yB

util.format(format[, ...args])

文字列のフォーマットもよく使う処理です。

例:

node.js
constutil=require("util")constf1=util.format('%s ,%d, %i, %f, %j, %o, %O','foo',-1234,01,3.14,{a:"a","b":"b"},{a:10},[1,2,3]);console.log(f1)//foo ,-1234, 1, 3.14, {"a":"a","b":"b"}, { a: 10 }, [ 1, 2, 3 ]

説明ページ:https://nodejs.org/api/util.html#util_util_format_format_args

util.TextEncoderクラス

文字列をUint8Array 配列に変換

node.js
const{TextEncoder}=require("util")constencoder=newTextEncoder();constuint8array=encoder.encode('こんにちは!\n Hello World!');// UTF-8のみサポートconsole.log(uint8array);/*Uint8Array [
  116,
  104,
  105,
  115,
  32,
  105,
  115,
  32,
  115,
  111,
  109,
  101,
  32,
  100,
  97,
  116,
  97 ]*/

util.TextDecoderクラス

Uint8Array配列を文字列に変換。

例:

node.js
const{TextEncoder,TextDecoder}=require("util")constencoder=newTextEncoder();constuint8array=encoder.encode('こんにちは!\n Hello World!');constoriginStr=newTextDecoder().decode(uint8array);//UTF-8console.log(originStr)/*
こんにちは!
 Hello World!
*/

URLSearchParamsクラス

URLのパラメータ処理も文字列関連ですが、URLSearchParamsを使うと処理しやすいです。
説明ページ:https://nodejs.org/api/url.html#url_class_urlsearchparams

例:

node.js
// 文字列からURLSearchParamsを生成constparams=newURLSearchParams('a=bar&b=baz');// 配列からconstparams2=newURLSearchParams([['user','abc'],['query','second']]);// MapからURLSearchParamsを生成constmap=newMap();map.set('user','abc');map.set('query','xyz');constparams3=newURLSearchParams(map);// パラメータすべて出力for(constnameofparams.keys()){console.log(name);}// 項目名で取得consta=params.get('a');// 項目を追加params.append('newParam','new');// 項目値を置換params.set('b','bbbbb');// 項目は存在するかをチェックconsole.log(params.has('b'));// 項目を削除params.delete('a');// 文字列で出力console.log(params.toString());/*
b=bbbbb&newParam=new
*/

以上

JIRA REST APIで課題情報を取得(jira-client-npm を使用)

$
0
0

はじめに

  • JIRA Softwareが10ユーザーまで無料で使えるようになったため家でもJIRAを触れるようになりました。
  • ということでREST APIの勉強をしつつ、結果を記事に残していこうと思います。
  • まずは課題の情報を取得する方法から。

実施環境

項目
JIRA バージョンJIRA Software フリープラン
プログラム言語Node.js v12.16.2
nodeモジュールjira-client v6.16.0
認証googleアカウント

事前準備

  • node.jsをインストールしておく。
  • JIRAで host / username / password / issueNumber(課題key)が必要になります。
  • Googleアカウント認証を使用している人は、APIトークンを取得する必要があります。
  • 取得の仕方はJIRA画面の右上プロフィールと設定(アバターマーク) → アカウント設定 → セキュリティー → APIトークンの作成と管理 →APIトークンを作成 (一度しか表示されないようなのでご注意を)

image.png

  • jira-client-npm のインストールはプロンプト画面で作業ディレクトリまで移動し下記を実行。
$ npm install jira-client

課題情報の取得

  • 作業ディレクトリにindex.jsファイルを作り下記内容を転記。
  • 使いそうな情報を全て載せてみましたので必要なところを適宜抜粋してご利用ください。
index.js
constJiraApi=require('jira-client');varissueNumber=""//keyを入力varcustomfield_n=""// Initializevarjira=newJiraApi({protocol:'https',host:'hostname.atlassian.net',//hostnameに置き換えusername:'username',//google認証の方はGmailアドレスに置き換えpassword:'password',//google認証の方はAPIトークンに置き換えapiVersion:'3',strictSSL:true});// 課題情報の取得jira.findIssue(issueNumber).then(function(issue){//customfieldの値を確認/*for (  var i = 10001;  i < 10100;  i++  ) {
            customfield_n = "customfield_" + i
            console.dir(customfield_n + ":" + JSON.stringify(eval("issue.fields." + customfield_n)))
        } *///      console.dir(issue.fields) //issue.fieldsをすべて確認console.log('プロジェクト名: '+issue.fields.project.name);console.log('要約: '+issue.fields.summary);console.log('タイプ: '+issue.fields.issuetype.name);console.log('優先度: '+issue.fields.priority.name);console.log('ステータス: '+issue.fields.status.name);console.log('エピック名: '+issue.fields.customfield_10011);console.log('エピックリンク: '+issue.fields.customfield_10014);if(issue.fields.parent==null){console.log('親:なし')}else{console.log('親: '+issue.fields.parent.key);}if(issue.fields.description!=null){console.log('説明:');console.log(issue.fields.description.content[0].content)}if(issue.fields.assignee==null){console.log('担当者: 未割当')}else{console.log('担当者: '+issue.fields.assignee.displayName);}if(issue.fields.reporter==null){console.log('報告者: 未割当')}else{console.log('報告者: '+issue.fields.reporter.displayName);}console.log('作成日: '+issue.fields.created);console.log('更新日: '+issue.fields.updated);console.log('期限 : '+issue.fields.duedate);if(issue.fields.components==""){console.log('コンポーネント:なし')}else{for(vari=0;i<issue.fields.components.length;i++){console.log('コンポーネント'+i+': '+JSON.stringify(issue.fields.components[i].name))}}if(issue.fields.labels==""){console.log('ラベル:なし')}else{console.log('ラベル: '+JSON.stringify(issue.fields.labels));}if(issue.fields.comment.total==0){console.log('コメント:なし');}else{console.log(issue.fields.comment.comments[issue.fields.comment.comments.length-1].body.content[0].content)}if(issue.fields.attachment==""){console.log('添付ファイル:なし')}else{for(vari=0;i<issue.fields.attachment.length;i++){console.log('添付ファイル'+i+': '+JSON.stringify(issue.fields.attachment[i].content))}}console.log('ウォッチ済: '+issue.fields.watches.isWatching);console.log('ストーリーポイント: '+issue.fields.customfield_10026);console.log('ストーリーポイント見積もり値: '+issue.fields.customfield_10016);}).catch(function(err){console.error(err);});

実行

$ node index.js

おわりに

  • 今後、課題の作成・データの書き換え等の情報もアップしていこうと思います。

新人に負けない本棚管理ページ その2(環境構築編)

$
0
0

目次

新人に負けない本棚管理サイト その1(プロローグ)
新人に負けない本棚管理サイト その2(環境構築編)

Node.js

Node.jsはサーバーサイドでJavaScriptを使えるようにするためのものです。
普通サーバーサイドの言語といえばPHPRuby(on Rails)、C#VBJavaなどがあります。
JavaScriptは本来クライアントサイドで使われる言語ですが、サーバーサイドまでJavaScriptで網羅することでぜーーんぶJavaScriptでできちゃうんです。

導入

Windows

公式のインストーラーをダウンロードして、画面に沿って進めていくだけです。

Ubuntu, Debian

公式のGitHubにやり方が書いてあります。
以下のコマンドを打つだけです。

# Ubuntuの場合
curl -sL https://deb.nodesource.com/setup_13.x | sudo-E bash -
sudo apt-get install-y nodejs

# Debianの場合(root権限で実行)
curl -sL https://deb.nodesource.com/setup_13.x | bash -
apt-get install-y nodejs

2020/04/19時点ではバージョン13が最新ですが、公式サイトではバージョン12が推奨されています。
バージョン12をインストールする場合は上記コマンドのsetup_13.xの部分をsetup_12.xに変更して実行してください。

React.js, Next.js

Next.jsの公式サイトの説明にしたがって行います。
Windowsならコマンドプロンプトで、Linuxなら何かしらのターミナルで以下のコマンドを実行します。

# 本棚管理サイトをつくるフォルダをつくる$ mkdir bookmanager

# ディレクトリを移動$ cd bookmanager

# Node.jsの初期設定をする# 実行後フォルダ内にpackage.json, package-lock.jsonというファイルができる$ npm init -y# React.jsとNext.jsのモジュールをインストール# なんかいっぱい文字が出ますが大丈夫です$ npm install react react-dom next

# pagesディレクトリをつくる# このフォルダめっちゃ大事!!$ mkdir pages

次にpackage.jsonファイルを書き換えます。
"scripts"と書いてあるところの{ }の中身を次の書き換えます。
すでに書いてある1行は消してしまって大丈夫です。

"dev": "next",
"build": "next build",
"start": "next start"

ここまできたら、次のコマンドを実行します。

$ npm run dev

しばらくするとターミナルがこんな感じになります。
私はRaspberryPi上で構築しています。

2020-04-19 235652.png

この状態になったら、一番したの行に書いてあるhttp://localhost:3000にアクセスしてみます。
すると...

2020-04-19 235926.png

orz ...

でも大丈夫!
スタートページを作っていないからです。

pagesフォルダにスタートページを作りましょう。

pages/index.js
exportdefaultfunctionIndex(){return(<div><h1>トップページだよ!</h1>
</div>
);}

念のためにCtrl-Cで停止して、もう一度npm run devします。
すると...

2020-04-20 001305.png

ヾ(。>﹏<。)ノ゙ヤッター!

長かったですがNext.jsの準備はこれで完了です。
お疲れさまでした。
これからは同様にpagesフォルダにWebページをおいていきます。

MariaDB

誰がこれで終わりと言った...?
まだデータベースが残ってるぅ!

MariaDBMySQLという「迷ったらコレ的DB」から派生したDBです。

参考サイト:Raspberry Pi にデータベースを構築する【MySQL,MariaDB】

Windowsの場合

公式サイトからインストーラを持ってきてインストールするだけです。
ホントにそれだけです!!
「適当すぎて草」「雑すぎワロタ」はダメです!!

Linuxの場合

インストール

$ sudo apt install mariadb-server mariadb-client

rootユーザーのログイン設定

まずログインします。

$ sudo mysql -u root

2020-04-20 003509.png

※私はrootパスワードを設定した後なのでコマンドに-pがついていますが、まだ必要ないです(-pはパスワードのp)
 Welcome to the MariaDB monitor. がすぐに表示されるとおもいます。

現在ログインしているユーザーを確認します。

MariaDB[(none)]>SELECTUSER();

2020-04-20 004004.png

ちゃんとlocalhostrootユーザーとしてログインできています。
リモートで接続した場合はクライアントのIPアドレスが出るのかな?

このままでは使用するデータベースを指定していない(none)ので、指定します。

MariaDB[(none)]>USEmysql;

2020-04-20 004303.png

mysqlというデータベースに接続できました。

プラグインを確認します。

MariaDB[(mysql)]>SELECTHost,User,Password,pluginFROMuser;

2020-04-20 004844.png

※ここでも設定済みなのでユーザーが2人いますが、ホントはrootだけです。すみません( ;∀;)

本来であればrootのpluginにunix_socketというものが入っています。
不要なので無効化しましょう。

MariaDB[(mysql)]>UPDATEuserSETplugin=''WHEREUser='root';

もう一度SELECTすると、画像のようにrootのpluginが空になっているのが確認できます。

一度ログアウトしてMariaDBを再起動した後、sudoなしでrootにログインできることを確認します。

MariaDB[(mysql)]>exit$ sudo service mariadb restart

2020-04-20 005933.png

※私はもうパスワードなしではrootにログインできないので、restartが済んだらmariadb -u rootを実行してログインできるか確認してください。

続いて、MariaDBの初期設定を行います。
質問形式で進みますが、最初にrootユーザーのパスワードをどうするか聞かれるので好きなパスワードを設定してください。
他の質問は基本的にYesと答えれば問題ないです。

$ mysql_secure_installation

設定が終了したら、パスワードなしでrootにログインしてみます。

$ mariadb -u root

2020-04-20 011130.png

めでたく弾かれました。
これでrootユーザーは守られました。

一般ユーザーのログイン設定(Windowsでも必要)

では、開発用の一般ユーザーを追加しましょう。
$ mariadb -u root -pを実行し、パスワードを入力してログインした後、ユーザーを作成します。
ユーザー作成が完了したらログアウトして、新しいユーザーにパスワードでログインできることを確認します。

-- XXX : 好きなユーザー名-- YYYYY : 好きなパスワードMariaDB[(none)]>CREATEUSER'XXX'@'%'IDENTIFIEDBY'YYYYY';MariaDB[(none)]>exit
$ mariadb -u XXX -p YYYYY

ログインができれば、一般ユーザーの設定は完了です。

(オプション)MariaDBへのリモート接続の許可

!!開発PC上にMariaDBがある場合は、以下の設定は不要です。!!

現段階では、localhostからしか接続できません。
これではDBに入ってるデータを確認したり新しくデータを登録したりするために、わざわざホストにログインしなければなりません。
そこでクライアントからホスト上のMariaDBに接続できるように設定します。

リモート接続にあたり、DB操作をGUIで行うためにA5-SQLをダウンロードします。
これは業務でも使用していて、もっっっっのすごい便利なツールです。
ダウンロードして最初に実行すると「リポジトリ or ポータブル」と聞かれると思いますが、

  • このツールをずっと使い続けるならリポジトリ
  • いまだけちょちょっと使うだけならポータブル

でいいと思います。

はじめに、A5-SQLからMariaDBに接続する設定をして、「テスト接続」してみます。
ホスト名はMariaDBがあるPCのIPアドレス、ユーザー名/パスワードは作成した一般ユーザーの名前とパスワードです。
rootユーザーではありません。

2020-04-20 012843.png

エラーが出ると思います。(Access deniedみたいなメッセージが出るはず)
これはMariaDBの設定として、リモートからの接続を許可していないためです。

じゃあ許可しちゃおうZE☆

設定ファイルはRaspberryPiの場合、/etc/mysql/mariadb.conf.d/50-server.cnfに設定があります。
ホストPCのターミナルで、以下のようにbind-addressの行をコメントアウトしてください。

/etc/mysql/mariadb.conf.d/50-server.cnf
bind-address            = 127.0.0.1
 ↓
#bind-address            = 127.0.0.1

sudo service mariadb restartでMariaDBを再起動し、再びA5-SQLでテスト接続してみます。
2020-04-20 013903.png

😆ヨッシャー!!

これでクライアントからMariaちゃんをお触りし放題です。

(オプション)Sambaを使ってファイル共有

!!開発PC上にNode.jsがある場合は、以下の設定は不要です。!!

私の環境ではすべてRaspberryPi上にあるため、RaspberryPiにSSH接続してvimでゴリゴリ書くなんてとてもやってられません!!
Sambaでファイル共有し、WindowsのVisual Studio Codeで編集できるようにします。

参考サイト:Raspberry Piを使ってファイルサーバーを立てる

「リンク先見てね」はしないと最初に言いましたが、
オプションですしやる人そんなにいなさそうなのでここはリンク先参照にさせてください...(´Д⊂グスン

次回

実際にページを作っていきます。
書籍一覧表のトップページ、書籍の登録ページ、書籍の編集ページをつくります。

rails db:createのエラー解決

$
0
0

非同期通信を学習するために、サンプルとしてToDoリストを管理するアプリを作成しようとしたところ、データベースを作成する段階で以下のようなエラーが出た。
ので、その備忘録として残しておこうと思う。

tech-camp@A-458 memo_app % rails db:create
rails aborted!
ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime. 
See https://github.com/rails/execjs for a list of available runtimes.
/Users/tech-camp/projects/todo_app/memo_app/config/application.rb:7:in `<top (required)>'
/Users/tech-camp/projects/todo_app/memo_app/Rakefile:4:in `require'
/Users/tech-camp/projects/todo_app/memo_app/Rakefile:4:in `<top (required)>'
/Users/tech-camp/projects/todo_app/memo_app/bin/rails:9:in `require'
/Users/tech-camp/projects/todo_app/memo_app/bin/rails:9:in `<top (required)>'
/Users/tech-camp/projects/todo_app/memo_app/bin/spring:13:in `<top (required)>'
bin/rails:3:in `load'
bin/rails:3:in `<main>'
(See full trace by running task with --trace)

エラーの原因

エラーの文面からすると、どうやらJavaScriptランタイムがないということらしい。
ランタイムというのは平たく言うと、プログラムを動かすための環境のようなもの。
要は「JavaScriptを動かすための環境(記述)がありません」と言っているのだ。
そりゃあデータベースも作れんわな。

ありがたいことに見るべきページも記載されているので、早速記載されている
https://github.com/rails/execjs
を覗いてみることに。
スクリーンショット 2020-04-20 10.31.26.png
なるほど、こいつらのどれかをインストールしてあげればいいのね。

解決策

今回は非同期通信の学習中だったので、非同期型のJavaScript環境である
Node.js
をランタイムとしてインストールすることに。

% brew install nodejs 

上記のコマンドを実行後、無事にデータベースを作成できました。

そもそもなんでランタイムが必要なのかとか、その辺も自力で調べてみると面白いかも。
自分も後で調べてみよう…。

gulpインストール後にgulp: command not foundのエラー

$
0
0

エラー内容

$ gulp ejs
bash: gulp: command not found
$ sudo npm install -g gulp
Password:
npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
/Users/mac/.npm-global/bin/gulp -> /Users/mac/.npm-global/lib/node_modules/gulp/bin/gulp.js
+ gulp@4.0.2
updated 2 packages in 9.078s
'/Users/mac/.npm-global/bin/gulp'

ココに着目。

対処法

/Users/mac/.npm-global/bin に path が通っていない事が原因でした。

$ export PATH=/Users/mac/.npm-global/bin:$PATH
$ gulp ejs
[10:53:38] Using gulpfile ~/Desktop/work/02-ejs/gulpfile.js
[10:53:38] Starting 'ejs'...
[10:53:38] Finished 'ejs' after 13 ms

OK!!

ExpressでTwitter APIを試す

$
0
0

はじめに

後輩がTwitterAPIの話してたら気になったので、叩いてみることにした。

Twitter Developer

概要

- Twitterアカウントがあれば自分用のAPIアクセスポイントを作って、TwitterAPIを叩ける

準備

Twitterアカウント

アカウント作成

※アカウント保有者は飛ばす

TwitterAPIアクセスポイント作成

  1. TwitterDeveloperにログイン
    1.png

  2. 右上ユーザー名プルダウンからAppsへ飛ぶ

  3. Create an appよりアプリ登録
    2.PNG
    アプリ登録画面
    3.PNG

テストなので↓こんな感じの内容↓で登録

項目説明
App Nametest自分の作るアプリの名前
Descriptionjust for testアプリの説明
Website URLhttp://test.comアプリのURL
Sign in with Twitterenabled
Callback URLhttp://localhost:3000サーバーURL。本番サーバー立てたら追加
Terms of service URL
Privacy policy URL
Organization name
Organization website URL
App usagetest test test test test test test test test test test test test test test test test test test test test test test test test test詳細なアプリ説明

下のCreateを押下

  1. 作成できたらKeys and tokensタブのGenrate keyを押下

5.PNG

:bangbang::bangbang::bangbang::bangbang::bangbang:重要:bangbang::bangbang::bangbang::bangbang::bangbang:
↓こんな画面↓が出てくるので、Access tokenAccess token secretをコピペ&メモして保存

6.PNG

作成完了
 7.PNG

使用するのは4つ
- Api key
- Api secret key
- Access token
- Access token secret

Node.js

Node.jsインストール

npmも使えるようにしておく
Node.jsダウンロードサイト

使用方法

- APIレファレンスから使いたいAPIを探す。

今回はトレンドを見ることにした
HttpRequest飛ばすだけでよさそう

API Reference contents 
GET trends/available   GET trends/closest
varTwitter=require('twitter');varclient=newTwitter({consumer_key:'',consumer_secret:'',access_token_key:'',access_token_secret:''});varparams={screen_name:'nodejs'};client.get('statuses/user_timeline',params,function(error,tweets,response){if(!error){console.log(tweets);}});

サーバーを立ててみる

仕様

サーバーのURLを叩いたらTwitterのトレンド結果がとりあえず画面いっぱいに出る。

ソースコード

package.json
{"name":"twitter_api_test","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1","start":"node ./index.js"},"author":"","license":"ISC","dependencies":{"express":"^4.17.1","twitter":"^1.7.1"}}
index.js
// server settingsconstexpress=require("express")constserver=express();constport=3000// twitter settingsconstTwitter=require('twitter');// さっき作ったAPIの設定constAPI_KEY="AAAAAAAAAA"constAPI_SECRET_KEY="BBBBBBBBBB"constACCESS_TOKEN="CCCCCCCCCC"constACCESS_TOKEN_SECRET="DDDDDDDDDD"consttwitter=newTwitter({consumer_key:API_KEY,consumer_secret:API_SECRET_KEY,access_token_key:ACCESS_TOKEN,access_token_secret:ACCESS_TOKEN_SECRET,});// routesserver.get("/",function(req,res,next){res.send("server is up")});server.get("/trends",function(req,res,next){// ここでTwitterAPIを叩くtwitter.get('trends/available',function(error,result,response){if(error){res.send(error)}else{res.send(result);// TwitterAPIの結果をそのまま返す}});})server.listen(port,function(){console.log('Listening on port '+port)});

結果

  1. npm installしたらnpm start
  2. localhost:3000にアクセス
    8.PNG

  3. localhost:3000/trendsにアクセス
    うまくいきました
    9.PNG


Fetch APIにタイムアウトをつける

$
0
0

Overview

Fetch APIを当たり前に使うわけですが、これタイムアウトって何秒なんだろう?という疑問からタイムアウトを実装することにしました。
タイムアウトがないとユーザーがブラウザ上でいつまでも応答を受け取れなかったり、クラウド上だとタイムアウトまで終了せずにコストが増大します:scream:
そんなことにならないよう、調査した結果を載せておきます。

Target reader

  • Fetch APIでタイムアウトや中断の方法を知りたい方。

Prerequisite

  • ブラウザはIEを除いた主要ブラウザとする。(IEだとFetch APIがないからpolyfill...)
  • Node.jsのバージョンはV10系とする。

Body

ブラウザ編

MDNでFetch APIのタイムアウトのオプションを探してみるが見つからない。
そんな中、取得の中止というものが見つかる。
https://developer.mozilla.org/ja/docs/Web/API/Fetch_API#Concepts_and_usage

取得の中止
ブラウザーは Fetch や XHR などの操作を完了前に中止させることができる AbortController および AbortSignal インターフェイス(つまり Abort API)に実験的に対応し始めています。詳しくはインターフェイスのページを参照してください。

AbortSignalのページには中止ボタンを押下したときに、通信を中断するサンプルコードがある。
https://developer.mozilla.org/ja/docs/Web/API/AbortSignal

また、動画のダウンロードを中断するDemoページのリンクもあるので、デベロッパーツールでネットワークを見ると中断ボタンで確かに中断することが確認できる。
https://mdn.github.io/dom-examples/abort-api/

さらにこのページのリンクから「Abort signals and fetch」というタイトルで、タイムアウトの実装コードも掲載されている。
https://developers.google.com/web/updates/2017/09/abortable-fetch

私がReactで使っている15秒でタイムアウトするコードも恐縮ながら掲載しておく:sweat_smile:
コード中のfetchのオプションであるsignalについてはFetch APIのページを見ると、中断のための入り口が設けられていることがわかる。
https://developer.mozilla.org/ja/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax

fetch.js
asyncfunctionfetchCore(url,option={}){// set timeout after 15sconstcontroller=newAbortController();consttimeout=setTimeout(()=>{controller.abort()},option.timeout||15000);try{constresponse=awaitfetch(url,{signal:controller.signal,// for timeout...option,});if(!response.ok){constdescription=`status code:${response.status} , text:${response.statusText}`;thrownewError(description);}returnresponse;}finally{clearTimeout(timeout);}}exportasyncfunctionfetchText(url,option){constresponse=awaitfetchCore(url,option);returnawaitresponse.text();}exportdefault{fetchText};

Node.js編

ブラウザは自前でタイマーの設定が必要になるが、無事にタイムアウトで終了させれることを確認。
今度はサーバーサイドのNode.jsではどうするかということを考えていく。
Node.jsはFetch APIが標準ではないため、node-fetchというパッケージを利用することになる。

node-fetchには以下のような文章がある。
https://github.com/node-fetch/node-fetch#features

Useful extensions such as timeout, redirect limit, response size limit, explicit errors for troubleshooting.

タイムアウト等の便利な拡張機能だと!!!

https://github.com/node-fetch/node-fetch#options

timeout: 0, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.

timeoutがありながらSignalを利用することをお勧めする…だと( ゚д゚)ポカーン
timeoutを作っていたけどブラウザのFetch APIがAbortController使ったタイムアウト可能になったからそっちに寄せたのかな?

sample.js
constfetch=require('node-fetch');constAbortController=require('abort-controller');constcontroller=newAbortController();consttimeout=setTimeout(()=>{controller.abort();},150);fetch('https://example.com',{signal:controller.signal}).then(res=>res.json()).then(data=>{useData(data);},err=>{if(err.name==='AbortError'){console.log('request was aborted');}}).finally(()=>{clearTimeout(timeout);});

これってほぼブラウザのFetchと一緒じゃないですか:relaxed:
結果としては、Node.jsにesmパッケージを導入すれば、Node.jsでもimport/exportが利用でき、ブラウザとコードを共通化できる。
ただし、Node.jsにはfetchやAbortControllerがビルトインされていないため、以下のようにnode-fetchabort-controllerをインストール必要がある。
逆に言えばブラウザのコードの最上部に二つのパッケージを取り込めばそれがNode.jsのコードになる!

fetchOnNode.js
constfetch=require('node-fetch');constAbortController=require('abort-controller');// 以下はブラウザ編のソースコードそのまま

おまけにjsdom編

jsdomはNode.jsでスクレイピングするときに便利なもので、読み込んだHTMLファイルのDOM操作をエミュレートして、ブラウザ上と同じようなことができたりする。
jsdomはfromURL()というメソッドがあり、urlを指定するとDomのPromiseを返してくれる。
このメソッドのタイムアウトはどうやって設定すればいいか調査してみた。
2016年のissueへの回答を引用する。

https://github.com/jsdom/jsdom/issues/596

The best way is to make the request yourself, then feed the resulting HTML to jsdom.

Google翻訳先生曰く

最善の方法は、リクエストを自分で作成し、結果のHTMLをjsdomにフィードすることです。

おま:joy:

ただもう少し調べてみると2019年にtimeoutというオプションを用意していた模様。
なぜかマージされずに放置されているが…時が経てばマージされるはず。
https://github.com/jsdom/jsdom/pull/2488

結局fromURL()は使わずにFetch APIで取得したHTMLのテキストをJSDOMコンストラクタの引数に入れるのがよさそう。
適所適材ですね。

// htmlはFetch APIで取得したテキストconstdom=newJSDOM(html,{});const{document}=dom.window;

Conclusion

これでもういつまで経っても応答が来ないという事態に遭遇することはないでしょう。
Fetch APIにタイムアウトのオプションが付くのが一番ですが、フロントもバックも同一のコードで済むのでこれはこれでいいかなと思います。

Have a great day!

puppeteer で ChromeのSpeechRecognitionAPI を使用する

$
0
0

はじめに

みなさんこちらの記事は読んでいただけたでしょうか?
.NET Frameworkで音声認識をしてzoomのビデオ画面に字幕を出す

サンプル

Zoomの自分の動画スペースに字幕を表示する方法なんですが、
先日とあるオンラインMeetUpイベントで使用してみたところ、
Webカメラに字幕を重ねて表示して配信することができました!!!実用的!

何言ってるかわからない問題

前回の記事にも載せましたが、.NET Frameworkの音声認識は
精度が良いとは言えたものではなく、とてもガバガバな認識結果を返してきます。

すると「え、それは何を表示してるの?」と、しゃべっている内容と表示文字列が
リンクしていると認識されない現象が発生します。致命的!

ほかの方法を試す

というわけで、違う方法を試してみることにします。今回、配信界隈を調査する中で
ゆかりねっと」という音声合成ソフトがあることを知りました。
調べるとどうも内部的に Google Chrome を使用しているそうです。

はて、GoogleChrome?と思って調べたところ SpeechRecognition API なるインタフェースが策定されていることを知りました。

SpeechRecognition - Web API | MDN

それは試さない手はない、ということで試してみました。

実現方法

字幕制御を nodejs で実装したので、同じく nodejs でいきます。

  • OS: Windows 10 64bit
  • ブラウザ: Google Chrome 81
  • node: v12.16.2

puppeteer の導入

今回はChromeを使用する予定なので puppeteer-coreで。

npm install --save puppeteer-core

実装

調べれば2, 3秒でわかる内容なんですが、今回学びがあったので備忘録程度に。

ブラウザとの双方向通信

https://github.com/puppeteer/puppeteer/issues/2331

puppeteerを使用して、ブラウザとデータのやり取りをする場合

  • puppeteer → page : page.evaluate
  • page → puppeteer : page.exposeFunction

というのを使用すると良いです。

awaitpage.exposeFunction("functionA",result=>{console.log(result);});

とあらかじめしておくことで、

awaitpage.evaluate(()=>{window.functionA("result from browser");});

このように、ブラウザ側で functionAが呼び出せるようになります。

headlessでSpeechRecognition APIが使用できない

puppeteer.launchのときに headless:trueとすることで、
ヘッドレス Chromeが使用でき、無駄にWindowが表示されないなど便利なのですが、
今回は startイベントが発生せず、どうも使用不可っぽいので、画面サイズ0でお茶を濁しました。

「マイクを使用する」ダイアログを表示させない

image.png

これですね。これツールを起動するたびに出ても仕方ないのでスキップするオプション
--use-fake-ui-for-media-streamを使用します。

ソース

というわけでソースコード載せておきます。ほぼ MDN のまんま。

https://github.com/yoh1496/puppeteer_speech_recognition

ただただ認識した文字列をコンソールに出力するだけです。

実行結果

前回記事でも試した

  • zoomに字幕を表示しています
  • 字幕のテストだよ
  • 字幕なんだけど、ちゃんと音声認識されてるんですかね

の3つを試しました。

image.png

良い感じですね。さすが!!

感想

結果は劇的に向上しました。が、1つ注意点として挙げられるのが
「オフライン実行できない」ということです。

Chromeの音声認識はサーバーに音声データを挙げて、サーバー側で結果を出力します。
なので、プライバシー問題もありますし、ネットワークの状況によっては使用不可だったり、レスポンスがめちゃくちゃ遅くなることも考えられます。

なので、今回は「クラウドサービスを使用したら音声認識精度が劇的に上がった」程度に捉えておくのがいいのかな、と思います。
サクッと効果が実感できるソリューションなのは間違いないですが。

終わりに

なぜか puppeteerで制御したけど意味あったのか・・・?

参考URL

Nest.jsでWebサービスを呼び出す方法

$
0
0

Nest.jsでWebサービスを呼び出す方法のメモです。このサンプルプログラムはGitHubに置いています。

種類バージョン備考
OSUbuntu18.04.01 LTS仮想で動かしています
nvm0.35.3もっと良さそうなのもあるかもしれないですが、特にこだわりなく使っています
Node.js12.16.22020/4時点最新のLTSを使っています
npm6.14.42020/4時点最新
nest cli7.1.22020/4時点最新
NEST7.0.02020/4時点最新
$ nest info

 _   _             _      ___  _____  _____  _     _____
| \ | |           | |    |_  |/  ___|/  __ \| |   |_   _|
|  \| |  ___  ___ | |_     | |\ `--. | /  \/| |     | |
| .` | / _ \/ __|| __|    | | `--.\| |    | |     | |
| |\ ||  __/\__ \| |_ /\__/ //\__/ /| \__/\| |_____| |_
\_| \_/ \___||___/ \__|\____/ \____/  \____/\_____/\___/


[System Information]
OS Version     : Linux 4.15
NodeJS Version : v12.16.2
NPM Version    : 6.14.4 

[Nest CLI]
Nest CLI Version : 7.1.2 

[Nest Platform Information]
platform-express version : 7.0.0
common version           : 7.0.0
core version             : 7.0.0

1.1. プロジェクト作成

プロジェクト作成予定のディレクトリからnest new <プロジェクト名>でプロジェクト作成。途中プロンプトでプロジェクト名"node-call-rest"を入力し、パッケージマネージャーに"npm"を選択。
git cloneでリポジトリをcloneした後に実行するとgit情報が上書きされてしまいました。 --skip-gitオプションを使ってもだめでした(まるで調べていないので、やり方が悪いだけかも)。

# From Node root directory$ nest new node-call-rest
⚡  We will scaffold your app in a few seconds..

? What name would you like to use for the new project? node-call-rest
CREATE node-call-rest/.eslintrc.js (599 bytes)
CREATE node-call-rest/.prettierrc (51 bytes)
CREATE node-call-rest/README.md (3370 bytes)
CREATE node-call-rest/nest-cli.json (64 bytes)
CREATE node-call-rest/package.json (1902 bytes)
CREATE node-call-rest/tsconfig.build.json (97 bytes)
CREATE node-call-rest/tsconfig.json (336 bytes)
CREATE node-call-rest/src/app.controller.spec.ts (617 bytes)
CREATE node-call-rest/src/app.controller.ts (274 bytes)
CREATE node-call-rest/src/app.module.ts (249 bytes)
CREATE node-call-rest/src/app.service.ts (142 bytes)
CREATE node-call-rest/src/main.ts (208 bytes)
CREATE node-call-rest/test/app.e2e-spec.ts (630 bytes)
CREATE node-call-rest/test/jest-e2e.json (183 bytes)

? Which package manager would you ❤️  to use? npm
✔ Installation in progress... ☕

🚀  Successfully created project node-call-rest
👉  Get started with the following commands:

$ cd node-call-rest
$ npm run start
              Thanks for installing Nest 🙏
     Please consider donating to our open collective
            to help us maintain this package.


   🍷  Donate: https://opencollective.com/nest

1.2. プロジェクト作成の動作確認

うまく作れているか実行して確認します。デフォルトでルートに"Hello World!"を返すようになっているので、それを使って確認します。

# From Project root directory$ npm run starg:debug
[1:27:21 PM] File change detected. Starting incremental compilation...

[1:27:21 PM] Found 0 errors. Watching for file changes.

Debugger listening on ws://127.0.0.1:9229/c6466736-e752-434a-a7be-a105e6731a7f
For help, see: https://nodejs.org/en/docs/inspector
[Nest] 11160   - 04/20/2020, 1:27:22 PM   [NestFactory] Starting Nest application...
[Nest] 11160   - 04/20/2020, 1:27:22 PM   [InstanceLoader] AppModule dependencies initialized +12ms
[Nest] 11160   - 04/20/2020, 1:27:22 PM   [RoutesResolver] AppController {}: +5ms
[Nest] 11160   - 04/20/2020, 1:27:22 PM   [RouterExplorer] Mapped {, GET} route +3ms
[Nest] 11160   - 04/20/2020, 1:27:22 PM   [NestApplication] Nest application successfully started +2ms

Curlで確認。Hello Worldが返ってきます。

$ curl localhost:3000
Hello World!

1.3. app.module.ts変更

src/app.module.tsを変更。HttpModuleをインポートします(1行目と7行目を変更)。

app.module.ts
import{Module,HttpModule}from'@nestjs/common';import{AppController}from'./app.controller';import{AppService}from'./app.service';import{RestController}from'./rest/rest.controller';@Module({imports:[HttpModule],controllers:[AppController,RestController],providers:[AppService],})exportclassAppModule{}

1.4. コントローラー作成

NEST cliでコントローラーrestを作成。

$ nest g controller rest
CREATE src/rest/rest.controller.spec.ts (479 bytes)
CREATE src/rest/rest.controller.ts (97 bytes)
UPDATE src/app.module.ts (322 bytes)

作成されたsrc/rest/rest.controller.tsを変更。
今回はGetリクエストを受けて、"httpbin.org"というサービスに対してPOSTリクエストを投げます。このサービスにBASIC認証は不要ですが、実際にはよく使うと思われるため付け加えておきました。

src/rest/rest.controller.ts
import{Controller,Get,HttpService}from'@nestjs/common';@Controller('rest')exportclassRestController{constructor(privatehttpService:HttpService){}@Get()asynccallRest(){consturl="http://httpbin.org/post";//authは今回は不要だが、入れるとしたらこんな形式constauth={auth:{username:'user name',password:'password'}};constbody={"contents":"test"};// APIコールtry{varresult=awaitthis.httpService.post(url,body,auth).toPromise();}catch(e){console.log(e.response);}console.log(result);returnresult.data;}}

2. 動作確認

2.1. Node.js起動

スクリプトを使ってNode.jsを起動します。

# From Project root directory$ npm run starg:debug

2.2. Getリクエストを投げる

curlを使ってlocalhost:3000/restにGetリクエストを投げます。"httpbin.org"へのpostリクエストの結果が返ってきます。見にくいので改行・インデントをして結果を整形しました。json以下にPostリクエストBodyがjsonで入っています。

$ curl localhost:3000/rest
{"args": {},
    "data": "{\"contents\":\"test\"}",
    "files": {},
    "form": {},
    "headers": {"Accept": "application/json, text/plain, */*",
        "Authorization": "Basic dXNlciBuYW1lOnBhc3N3b3Jk",
        "Content-Length": "19",
        "Content-Type": "application/json;charset=utf-8",
        "Host": "httpbin.org",
        "User-Agent": "axios/0.19.2",
        "X-Amzn-Trace-Id": "Root=1-5e9d303c-3f9f4b3c5621034ae617c097"},
    "json": {"contents": "test"},
    "origin": "<my ip address>",
    "url": "http://httpbin.org/post"}

npm run starg:debugを実行したターミナルでは、ログにもっと細かい情報を出しています。

[2:16:40 PM] File change detected. Starting incremental compilation...

[2:16:41 PM] Found 0 errors. Watching for file changes.

Debugger listening on ws://127.0.0.1:9229/2c52a7c8-dc62-485c-90c9-94b91200de8e
For help, see: https://nodejs.org/en/docs/inspector
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [NestFactory] Starting Nest application...
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [InstanceLoader] HttpModule dependencies initialized +16ms
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [InstanceLoader] AppModule dependencies initialized +2ms
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [RoutesResolver] AppController {}: +5ms
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [RouterExplorer] Mapped {, GET} route +3ms
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [RoutesResolver] RestController {/rest}: +1ms
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [RouterExplorer] Mapped {/rest, GET} route +1ms
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [NestApplication] Nest application successfully started +4ms
{
  status: 200,
  statusText: 'OK',
  headers: {date: 'Mon, 20 Apr 2020 05:16:44 GMT',
    'content-type': 'application/json',
    'content-length': '537',
    connection: 'close',
    server: 'gunicorn/19.9.0',
    'access-control-allow-origin': '*',
    'access-control-allow-credentials': 'true'},
  config: {
    url: 'http://httpbin.org/post',
    method: 'post',
    data: '{"contents":"test"}',
    headers: {
      Accept: 'application/json, text/plain, */*',
      'Content-Type': 'application/json;charset=utf-8',
      'User-Agent': 'axios/0.19.2',
      'Content-Length': 19
    },
    auth: { username: 'user name', password: 'password'},
    transformRequest: [[Function: transformRequest] ],
    transformResponse: [[Function: transformResponse] ],
    timeout: 0,
    adapter: [Function: httpAdapter],
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    validateStatus: [Function: validateStatus],
    cancelToken: CancelToken { promise: [Promise], reason: [Cancel] }},
  request: ClientRequest {
    _events: [Object: null prototype] {
      socket: [Function],
      abort: [Function],
      aborted: [Function],
      error: [Function],
      timeout: [Function],
      prefinish: [Function: requestOnPrefinish]
    },
    _eventsCount: 6,
    _maxListeners: undefined,
    outputData: [],
    outputSize: 0,
    writable: true,
    _last: true,
    chunkedEncoding: false,
    shouldKeepAlive: false,
    useChunkedEncodingByDefault: true,
    sendDate: false,
    _removedConnection: false,
    _removedContLen: false,
    _removedTE: false,
    _contentLength: null,
    _hasBody: true,
    _trailer: '',
    finished: true,
    _headerSent: true,
    socket: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'httpbin.org',
      _readableState: [ReadableState],
      readable: true,
      _events: [Object: null prototype],
      _eventsCount: 6,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: false,
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: null,
      _server: null,
      parser: null,
      _httpMessage: [Circular],
      [Symbol(asyncId)]: 15,
      [Symbol(kHandle)]: [TCP],
      [Symbol(kSetNoDelay)]: false,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0
    },
    connection: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'httpbin.org',
      _readableState: [ReadableState],
      readable: true,
      _events: [Object: null prototype],
      _eventsCount: 6,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: false,
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: null,
      _server: null,
      parser: null,
      _httpMessage: [Circular],
      [Symbol(asyncId)]: 15,
      [Symbol(kHandle)]: [TCP],
      [Symbol(kSetNoDelay)]: false,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0
    },
    _header: 'POST /post HTTP/1.1\r\n' +
      'Accept: application/json, text/plain, */*\r\n' +
      'Content-Type: application/json;charset=utf-8\r\n' +
      'User-Agent: axios/0.19.2\r\n' +
      'Content-Length: 19\r\n' +
      'Host: httpbin.org\r\n' +
      'Authorization: Basic dXNlciBuYW1lOnBhc3N3b3Jk\r\n' +
      'Connection: close\r\n' +
      '\r\n',
    _onPendingData: [Function: noopPendingOutput],
    agent: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      defaultPort: 80,
      protocol: 'http:',
      options: [Object],
      requests: {},
      sockets: [Object],
      freeSockets: {},
      keepAliveMsecs: 1000,
      keepAlive: false,
      maxSockets: Infinity,
      maxFreeSockets: 256,
      [Symbol(kCapture)]: false},
    socketPath: undefined,
    method: 'POST',
    insecureHTTPParser: undefined,
    path: '/post',
    _ended: true,
    res: IncomingMessage {
      _readableState: [ReadableState],
      readable: false,
      _events: [Object: null prototype],
      _eventsCount: 3,
      _maxListeners: undefined,
      socket: [Socket],
      connection: [Socket],
      httpVersionMajor: 1,
      httpVersionMinor: 1,
      httpVersion: '1.1',
      complete: true,
      headers: [Object],
      rawHeaders: [Array],
      trailers: {},
      rawTrailers: [],
      aborted: false,
      upgrade: false,
      url: '',
      method: null,
      statusCode: 200,
      statusMessage: 'OK',
      client: [Socket],
      _consuming: true,
      _dumped: false,
      req: [Circular],
      responseUrl: 'http://user%20name:password@httpbin.org/post',
      redirects: [],
      [Symbol(kCapture)]: false},
    aborted: false,
    timeoutCb: null,
    upgradeOrConnect: false,
    parser: null,
    maxHeadersCount: null,
    reusedSocket: false,
    _redirectable: Writable {
      _writableState: [WritableState],
      writable: true,
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      _options: [Object],
      _redirectCount: 0,
      _redirects: [],
      _requestBodyLength: 19,
      _requestBodyBuffers: [],
      _onNativeResponse: [Function],
      _currentRequest: [Circular],
      _currentUrl: 'http://user%20name:password@httpbin.org/post',
      [Symbol(kCapture)]: false},
    [Symbol(kCapture)]: false,
    [Symbol(kNeedDrain)]: false,
    [Symbol(corked)]: 0,
    [Symbol(kOutHeaders)]: [Object: null prototype] {
      accept: [Array],
      'content-type': [Array],
      'user-agent': [Array],
      'content-length': [Array],
      host: [Array],
      authorization: [Array]
    }},
  data: {
    args: {},
    data: '{"contents":"test"}',
    files: {},
    form: {},
    headers: {
      Accept: 'application/json, text/plain, */*',
      Authorization: 'Basic dXNlciBuYW1lOnBhc3N3b3Jk',
      'Content-Length': '19',
      'Content-Type': 'application/json;charset=utf-8',
      Host: 'httpbin.org',
      'User-Agent': 'axios/0.19.2',
      'X-Amzn-Trace-Id': 'Root=1-5e9d303c-3f9f4b3c5621034ae617c097'},
    json: { contents: 'test'},
    origin: 'my ip address',
    url: 'http://httpbin.org/post'}}

-bash: npm: command not found の普通じゃない対処法

$
0
0

環境

Mac OS X ver10.14.5

経緯

nodebrew でインストールしたあと、パスを通して、一通り npm -vnode -vでバージョン確認できていました。
PCを再起動が原因と思われるが、-bash: npm: command not foundが出るようになってしまった。
パスの設定を見直して、ターミナルの再起動、Macの再起動を図るも改善せず。
ただ、nodebrew -vは認識する。。。なぜ???

調査する過程で、インストールの参考サイトを見ているときに、別な確度に気づいたので共有します。

nodebrew インストール参考サイト

https://qiita.com/mame_daifuku/items/373daf5f49ee585ea498

解決方法

結論から書くと、パスの指定方法を変更しました。
~/.bash_profileに以下を追記。

mac
export PATH=/usr/local/var/nodebrew/current/bin:$PATH

通常、どの参考サイトを見ても以下の記述が一般的です。というか、僕もこれで動いていたんですが・・・。

mac
export PATH=$HOME/.nodebrew/current/bin:$PATH

(ちなみに、わかってると思いますが、:$PATHを省略すると、大変なことに・・・w)

これを何度も行いますが、一向に改善せず。
ファイルも実際にあることを確認してます。
ターミナルも再起動。
なんなら、source ~/.bash_profileも・・・。

うーん、なんでだろう?

参考サイトの手順をもう一度見直す。。。
試しに、以下を実行してみました。

mac
nodebrew setup

nodebrewのセットアップです。

すると、以下の記述が・・・!

mac
Fetching nodebrew...
Installed nodebrew in /usr/local/var/nodebrew

========================================
Export a path to nodebrew:

export PATH=/usr/local/var/nodebrew/current/bin:$PATH
========================================

あれ、、、
PATHの記述がちょっと違う・・・?
まさか・・・と思い、冒頭の通り追記すると、無事にnpm -vnode -vが確認できました。

めでたしめでたし。。。

なんだか行き詰まった方の参考になれば幸いです。

初心者がNightwatchの導入からチュートリアル終了までやってみる

$
0
0

はじめに

この記事はほぼ全てnightwatchjs.orgの内容そのままです。
余力があるならそちらをご覧になってもよいかと思います。
想定する読者は、(自分のように)初めてプログラミングするという方です。

Nightwatchとは?

Nightwatch.js is an automated end-to-end testing framework for web applications and websites.

Nightwatch.jsは、WebアプリケーションおよびWebサイト用の自動化されたエンドツーエンドのテストフレームワークです。
要は、ブラウザを実際に動かして自動テスト(操作)してくれるって事のようです。
エンドツーエンド(E2E)についてはこの記事が分かりやすかったです。

Node.jsのインストール

  1. nodejs.orgから環境にあったインストーラーをダウンロード
  2. インストーラーを起動
  3. インストーラーの指示通りに進める(npmのインストールを外さないこと、デフォルトでチェックが入っています)

Node.jsとnpmがインストールできたか確かめる

Node.jsの確認

コマンドプロンプトで以下のように入力します。

$node -v

で、結果がこんな感じになればOKです。

$node -vv12.16.2

npmの確認

npmも同じようにコマンドプロンプトで以下のように入力します。

$npm -v

こちらもこんな感じに表示されればOKです。

$npm -v6.14.4

うまくいかない場合は、インストールできていないかpathが通っていない可能性があるので、
エラーログを読みつつ、再度Node.jsのインストールを試してください。

npmをつかってNightwatchをインストールする

ここからNightwatchをインストールしていきます。
さっくり消せるように専用のフォルダを作って作業していきます。

Nightwatch用のフォルダを作る

好きな場所にフォルダを作ってください。
今回は以下のようにドキュメントの下に作りました。
フォルダ名はNightwatchにしています。

C:\Users\(ユーザー名)\Documents\Nightwatch

作業用フォルダにNightwatchをインストールする

作業フォルダでコマンドプロンプトを起動

作業用フォルダのアドレスバーにcmdと打ち込めばそのフォルダで起動します。
こんな感じになります。

C:\Users\(ユーザー名)\Documents\Nightwatch>

package.jsonを作る

npmを使ってインストールするために、作業フォルダにpackage.jsonをつくります。
package.jsonについてはこの記事が分かりやすかったです。
作業フォルダのコマンドプロンプトで以下のように入力してください。

C:\Users\(ユーザー名)\Documents\Nightwatch>npm init -y

結果は以下のようになります。

C:\Users\(ユーザー名)\Documents\Nightwatch>npm init -yWrote to C:\Users\(ユーザー名)\Documents\Nightwatch\package.json:

{
  "name": "Nightwatch",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

npmを使ってNightwatchをインストールする

作業フォルダのコマンドプロンプトで以下のように入力します。
単体で使うなら--save-devは不要ですが、Nightwatchの存在理由(テストフレームワーク)を踏まえてマニュアル通りにしておきます。

C:\Users\(ユーザー名)\Documents\Nightwatch>npm install nightwatch --save-dev

結果は以下のようになります。

C:\Users\(ユーザー名)\Documents\Nightwatch>npm install nightwatch --save-devnpm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142

>ejs@2.7.4 postinstall C:\Users\(ユーザー名)\Documents\Nightwatch\node_modules\ejs
>node ./postinstall.js

Thank you for installing EJS: built with the Jake JavaScript build tool (https://jakejs.com/)

npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN Nightwatch@1.0.0 No description
npm WARN Nightwatch@1.0.0 No repository field.

+ nightwatch@1.3.4
added 264 packages from 669 contributors and audited 439 packages in 11.678s

15 packages are looking for funding
  run `npm fund` for details

found 1 low severity vulnerability
  run `npm audit fix` to fix them, or `npm audit` for details

npmを使ってWebドライバーをインストールする

今回はChromeのみでのテストを予定しています。
なので、seleniumドライバーは不要です。
Webドライバーは使うブラウザのバージョンと対応する必要があるので、調べてからインストールしていきます。

使っているChromeのバージョンを確認する

以下の手順で確認できます。
より詳しい説明は公式のヘルプにあります。

  1. パソコンで Chrome を開きます。
  2. 右上のその他アイコンをクリックします。
  3. [ヘルプ] 次に [Google Chrome について] をクリックします。
  4. [Google Chrome] という見出しの下に表示される番号が現在のバージョン番号です。

このバージョン番号と一致したChromeドライバーのバージョンをインストールします。
(Chromeを最新バージョンにしていれば、Chromeドライバーも最新バージョンで良いはずです。)

対応したバージョンのChromeドライバーのインストール

対応したバージョンのChromeドライバーインストールしていきます。

#最新バージョンの場合
C:\Users\(ユーザー名)\Documents\Nightwatch>npm install chromedriver --save-dev#特定のバージョンの場合
C:\Users\(ユーザー名)\Documents\Nightwatch>npm install chromedriver@XX.XX.X --save-dev

結果は以下のようになります。

C:\Users\(ユーザー名)\Documents\Nightwatch>npm install chromedriver --save-dev>chromedriver@81.0.0 install C:\Users\(ユーザー名)\Documents\Nightwatch\node_modules\chromedriver
>node install.js

Current existing ChromeDriver binary is unavailable, proceeding with download and extraction.
Downloading from file:  https://chromedriver.storage.googleapis.com/81.0.4044.69/chromedriver_win32.zip
Saving to file: C:\Users\(ユーザー名)\AppData\Local\Temp\81.0.4044.69\chromedriver\chromedriver_win32.zip
Received 1040K...
Received 2080K...
Received 3120K...
Received 4160K...
Received 4290K total.
Extracting zip contents.
Copying to target path C:\Users\(ユーザー名)\Documents\Nightwatch\node_modules\chromedriver\lib\chromedriver
Done. ChromeDriver binary available at C:\Users\(ユーザー名)\Documents\Nightwatch\node_modules\chromedriver\lib\chromedriver\chromedriver.exe
npm WARN Nightwatch@1.0.0 No description
npm WARN Nightwatch@1.0.0 No repository field.

+ chromedriver@81.0.0
added 58 packages from 95 contributors and audited 539 packages in 8.679s

17 packages are looking for funding
  run `npm fund` for details

found 1 low severity vulnerability
  run `npm audit fix` to fix them, or `npm audit` for details

Nightwatchの実行

インストールが終わったので、テストを実行してみます。

Nightwatchの設定ファイルを作る

Nightwatchの設定ファイル(nightwatch.conf.js)を作っていきます。
チュートリアルのこのパートにあたります。

設定ファイル(nightwatch.conf.js)を作る

作業フォルダ(Nightwatch)の下にnightwatch.conf.jsというファイルを作ります。
中身は以下のようにします。
nightwatch.jsonではなくnightwatch.conf.jsにした理由は、拡張性が高く、両方存在したときに優先されるらしいためです。

nightwatch.conf.js
module.exports={src_folders:["tests"],webdriver:{start_process:true,port:9515,server_path:require("chromedriver").path,},test_settings:{default:{desiredCapabilities:{browserName:"chrome",},},},};

自動テストを作る

実際に動かすテストを書いていきます。
既に入っているサンプルテストでも良いのですが、せっかくなので作ります。

テストフォルダを作る

先ほど作った作業フォルダの下にテストフォルダを作成します。
この中に作った自動テストを格納していきます。
フォルダ名はtestsとして、以下のように作りました。

C:\Users\(ユーザー名)\Documents\Nightwatch\tests

このテストフォルダが、設定ファイル(nightwatch.conf.js)のsrc_folders: [tests]にあたります。
src_foldersにテストフォルダを記載しない場合は、テスト実行時の引数で指定することになります。

テストを作る

テストを実際に作ります。
以下のコードはサンプルテストほぼそのままです。
google.jsでも良いですが、わかりやすそうので、ecosia.jsを利用しました。

以下のファイルをテストフォルダ(tests)の中に入れてください。

ecosia.js
describe("Ecosia.org Demo",function(){before((browser)=>browser.url("https://www.ecosia.org/"));test("Demo test ecosia.org",function(browser){browser.waitForElementVisible("body").assert.titleContains("Ecosia").assert.visible("input[type=search]").setValue("input[type=search]","nightwatch").assert.visible("button[type=submit]").click("button[type=submit]").assert.containsText(".mainline-results","Nightwatch.js");});after((browser)=>browser.end());});

テストを実行する

作ったテストを実行して、動作するか見ていきます。

Nightwatchのpathを通す

Nightwatchを簡単に実行できるようにpathを通します。
pathを通すの意味についてはこの記事が分かりやすかったです。

  1. windowsの検索窓に「path」と入力
  2. 「環境変数を編集」が出てくるので選択
  3. 「ユーザーの環境変数」の「Path」を選択
  4. 「編集」を選択
  5. 「環境変数名の編集」が出てくるので「新規」を選択
  6. 以下のパスを追加(nightwatchの実行ファイルがある場所です)
C:\Users\(ユーザー名)\Documents\Nightwatch\node_modules\.bin

サンプルテストを実行してみる

作業フォルダでコマンドプロンプトを起動し、以下を入力してください。

C:\Users\(ユーザー名)\Documents\Nightwatch>nightwatch

Chromeが起動し、ecosia.orgを開き、nightwatchと検索してくれるはずです。
また、結果は以下のようになります。
ここまでできれば、チュートリアルに記載されている内容は完了となります。

C:\Users\(ユーザー名)\Documents\Nightwatch>nightwatch

[Ecosia.org Demo] Test Suite
============================
\ Connecting to localhost on port 9515...

DevTools listening on ws://127.0.0.1:53905/devtools/browser/a6462e23-1c06-4ce8-9d1b-2cbba2c0e66d
i Connected to localhost on port 9515 (2615ms).
  Using: chrome (81.0.4044.113) on Windows platform.

Running:  Demo test ecosia.org

√ Element <body>was visible after 28 milliseconds.
√ Testing if the page title contains 'Ecosia' (12ms)
√ Testing if element <input[type=search]>is visible (28ms)√ Testing if element <button[type=submit]>is visible (26ms)√ Testing if element <.mainline-results>contains text 'Nightwatch.js'(131ms)
OK. 5 assertions passed. (2.17s)

おわりに

これでNightwatchのチュートリアルは終了です。
内容の不備などあればコメントしていただけますと大変勉強になります。
ここまで初心者の記事を読んでいただきありがとうございました。

次回は開発ガイドを読みながら、テストを書いてみたいと思います。

Viewing all 8814 articles
Browse latest View live