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

VagrantにNode.js、node-sassをインストールして自動コンパイル

$
0
0

仕事で、node-sassを使うことになったので、インストール手順をまとめます。

■環境
OS: macOS Mojave 10.14.5
VirtualBox: 6.0.10
Vagrant: 2.2.5
ゲストOS: CentOS7
Node.js: v12.13.0
nvm: v0.35.0

nvmのインストール

nvmはNode.jsのバージョン管理(インストール・切り替え)ができるツールです。

githubに書いてある通りにインストールしていきます。
https://github.com/nvm-sh/nvm

$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.0/install.sh | bash
$ export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
$ [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
$ nvm -v
Node Version Manager (v0.35.0)

バージョンが表示されれば、インストールOK。

Node.jsインストール

nvmがインストールできたので、Node.jsをインストールします。

$ nvm install --lts

--ltsをつけることで、最新のLTSをインストールできます。

LTSとは、Long Term Supportの略で、「長期サポート」的な意味で、ダウンロードページに「推奨版」と書いてあります。
https://nodejs.org/ja/download/

記事執筆時点でのLTSは12.13.0。

ちなみに、下記のコマンドでインストールできるバージョン一覧を確認できます。

$ nvm ls-remote

バージョンが表示されれば、インストールOK。

$ node -v
v12.13.0

node-sassのインストール

Node.jsがインストールできたので、次にnode-sassをインストールします。
npmというコマンドを使いますが、これはNode.js用のパッケージマネージャーです。
(Node.jsをインストールすれば使用できます。)

$ npm install -g node-sass

オプションに-gを付ける事で、グローバルインストールできます。

package.jsの編集

package.jsというファイルができているので、下記のように記述します。
これは「npm run start」というコマンドを使って、package.json の scripts に指定された内容を実行するためです。
これにより、sassの自動コンパイルが可能になります。

"scripts": {
    "watch:sass": "node-sass src/sass -o dist/sass -w -r --output-style compact"
}

この場合、src/sassディレクトリ内の監視をスタートし、Sassファイルが更新されたらコンパイルします。

nodeの起動

起動します。

$ npm run start

下記のコマンドでもOKです。

$ npm start

npm install --save で JSON.parse エラーが出る場合は package.json が壊れているので 空 JSON でも書き込もう #npm #node

$
0
0

Error

$ npm install --save puppeteer
npm ERR! file /Users/yumainaura/.ghq/github.com/GuildWorks/insides/insides-api/package.json
npm ERR! code EJSONPARSE
npm ERR! JSON.parse Failed to parse json
npm ERR! JSON.parse Unexpected end of JSON input while parsing near ''
npm ERR! JSON.parse Failed to parse package.json data.
npm ERR! JSON.parse package.json must be actual JSON, not just JavaScript.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/yumainaura/.npm/_logs/2019-11-04T04_04_46_126Z-debug.log

解決

echo '{}' > package.json

npm install --save puppeteer

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2665

Electronを使ってオセロアプリを作ろう #2

$
0
0

01.環境構築
02.基本開発
03.発展

設計

前章では設計を行わず大雑把に環境構築を行いましたが
ここで大まかな仕様を決めます。
オセロで登場するオブジェクトはゲーム盤、石、試合ルール、プレイヤーと言ったところでしょうか?
ゲーム盤は8x8のマス目があり2人のプレイヤーが石を交互に置き合い試合をします。
それぞれのオブジェクトがさらに詳細に仕様をつめていきます。

初期状態

まずは仕様をまとめ、Jestのテストを書きます。
黒石を1、白石を-1、置かれていない場所は0とすることにします。
オセロはゲーム開始時点で中央四つに石が置かれています。
これをテストにすると下のようになります。
boardは8x8なので[3,3][4,4][3,4][4,3]が最初が石を置く位置です。

./test/reversi.test.js

import Game from '../renderer/game’ ; 
describe('Game', function () {     
    : 
  describe('constructor()', () => {
    it('初期状態で中央に石が置かれている', () => {
      const game = new Game()
      expect(game.board[3][3]).toBe(-1)
      expect(game.board[4][4]).toBe(-1)
      expect(game.board[3][4]).toBe(1)
      expect(game.board[4][3]).toBe(1)
    })
  })
}) 

boardを定義していませんので勿論エラーになるはずです。
次にこれを実装します。また盤の状態を取得できるようにゲッターも作成します。

./renderer/board.js

export default class Board { 
  constructor(){ 
    this.player = 1; 
    this.board = [ 
      [0,0,0,0,0,0,0,0], 
      [0,0,0,0,0,0,0,0], 
      [0,0,0,0,0,0,0,0], 
      [0,0,0,-1,1,0,0,0], 
      [0,0,0,1,-1,0,0,0], 
      [0,0,0,0,0,0,0,0], 
      [0,0,0,0,0,0,0,0], 
      [0,0,0,0,0,0,0,0] 
    ]; 
  } 
  put(x,y) 
    this.player = -this.player; 
   
} 

ひとまずデータの初期状態ができました。

表示

初期状態ができましたのでこれを表示してみましょう。
表示部分は仕様変更が容易になるように自動テストは簡易にしておくのが望ましいです。
ここではテストを省略して後ほど行うようにします。

描画用にhtmlにcanvasを設置します。

html

<body> 
    <canvas id="canv" width="500px" height="500px"></canvas> 
    <script src="./reversi.js" ></script> 
</body> 

javascriptを作成します。
getContextで画面上の描画領域canvから2D描画のための情報を取得します。

reversi.js

import Game from '../renderer/game' ; 
import Draw from '../renderer/draw' 
var context = document.getElementById("canv").getContext('2d'); 
var game = new Game() 
var draw = new Draw(context) 
draw.draw_discs(board.board) 

描写のメインはdraw_boardとdraw_discsです。
canvasに対して四角と円でオセロ盤を表示しています。

const COLOR_LINE = "#FFFFFF"; 
const COLOR_BOARD = "#00BB33"; 
const COLOR_WHITE = "#FFFFFF"; 
const COLOR_BLACK = "#000000"; 
const CELL_SIZE = 60; 
const DISC_SIZE = 29; 
export default class Drawing { 
  constructor(context){ 
    this.context = context 
    this.draw_board() 
  } 
  draw_board(){ 
    this.context.beginPath() 
    this.context.clearRect(0,0,500,500); 
    this.context.lineWidth = 1; 
    this.context.fillStyle = COLOR_BOARD; 
    for (var x = 0; x < 8; x++) { 
        for (var y = 0; y < 8; y++) { 
            this.context.strokeStyle = COLOR_LINE; 
            this.context.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE); 
            this.context.strokeRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE); 
        } 
    } 
  } 
  draw_discs(board){ 
    this.board = board 
    for (var x = 0; x < 8; x++) { 
        for (var y = 0; y < 8; y++) { 
          this.context.beginPath() 
          if (this.board[x][y] == 1 ) { 
            this.context.fillStyle = COLOR_BLACK; 
            this.context.arc( 
              x * CELL_SIZE + CELL_SIZE/2, 
              y * CELL_SIZE + CELL_SIZE/2, 
              DISC_SIZE, 
              0, 
              Math.PI*2, 
              false 
            ); 
            this.context.fill(); 
          } 
          else if (this.board[x][y] == -1 ) { 
            this.context.fillStyle = COLOR_WHITE; 
            this.context.arc( 
              x * CELL_SIZE + CELL_SIZE/2, 
              y * CELL_SIZE + CELL_SIZE/2, 
              DISC_SIZE , 
              0, 
              Math.PI*2, 
              false 
            ); 
            this.context.fill(); 
          } 
        } 
    } 
  } 
} 

石を置く

put関数にオセロルールを実装していきます。
まずはテストに仕様を記述しましょう。

./test/reversi.test.js

  describe('put 石置き関数', () => { 
    it('異色を挟んで反対側に同色がある場合石が置け、ターンが切り替わる', () => {
      const game = new Game()
      game.put(2,3)
      expect(game.board[2][3]).toBe(1)
      game.put(4,2)
      expect(game.board[4][2]).toBe(-1)
    })
    it('置いてあるマスには石は置けずターンが変わらない', () => {
      const game = new Game()
      const def_player = game.player
      game.put(3,3)
      expect(def_player).toBe(game.player)
      game.put(3,4)
      expect(def_player).toBe(game.player)
      game.put(4,3)
      expect(def_player).toBe(game.player)
      game.put(4,4)
      expect(def_player).toBe(game.player)
    })
    it('異色が触れてない場所には置けない', () => {
      const game = new Game()
      const def_player = game.player
      game.put(0,0)
      expect(def_player).toBe(game.player)
      game.put(0,7)
      expect(def_player).toBe(game.player)
      game.put(7,0)
      expect(def_player).toBe(game.player)
      game.put(7,7)
      expect(def_player).toBe(game.player)
    })
  }) 

毎回mainのテストが実行される必要はないためスキップするようにしましょう

./test/main.test.js

describe.skip('Window', function () { 

実行時にskipedが1になりました。
また石置き関数がfailになりました。

実装

実装を進めていきます。 以下の実装には間違いがあります。
jestもfailのままです。
テストや出力を使って間違いを特定してみましょう。

  put(x,y) { 
    if( this.canPut(x,y) ){ 
      this.board[x][y] = this.player 
      this.player = -this.player 
    } 
  } 
  canPut(x,y) { 
    if( this.board[x][y] != 0 ) return false 
    for( let di_x = -1 ; di_x <= 1 ; di_x++ ){ 
      for( let di_y = -1 ; di_y <= 1 ; di_y++ ){ 
        //中心以外の8方向をチェックする 
        if ( di_x === 0 && di_y === 0 ) continue 
        //盤外の場合は次へ 
        if ( x+di_x < 0 || y+di_y < 0 || 7 < x+di_x || 7 < y+di_y ) continue 
        //接してる石が相手色でなければ次へ 
        if ( this.board[x+di_x][y+di_y] === -this.player ) continue 
        //再帰チェック 
        if( this.canPutSub( x+di_x, di_x, y+di_y, di_y ) ) return true 
      } 
    } 
    return false 
  } 
  canPutSub(x,di_x,y,di_y) { 
    //盤外の場合はfalseを返しこの方向のチェックを終わる 
    if ( x+di_x < 0 || y+di_y < 0 || 7 < x+di_x || 7 < y+di_y ) return false 
    //石がない場合はfalseを返しこの方向のチェックを終わる 
    if ( this.board[x+di_x][y+di_y] === 0 ) return false 
    //自色があればtrueを返す 
    if ( this.board[x+di_x][y+di_y] === this.player ) return true 
    //次の石のチェックへ 
    return this.canPutSub( x+di_x, di_x, y+di_y, di_y ) 
  } 

一番最下層の再帰関数からチェックしてみます。

    it.only('再帰関数テスト', () => { 
      const board = new Board() 
      expect(game.canPutSub(3,0,3,1)).toBeTruthy()
      expect(game.canPutSub(4,0,1,-1)).toBeFalsy()
      expect(game.canPutSub(5,1,2,0)).toBeFalsy()
      expect(game.canPutSub(3,-1,2,0)).toBeFalsy()

      expect(game.canPutSub(5,1,3,1)).toBeFalsy()
      expect(game.canPutSub(5,1,1,-1)).toBeFalsy()
      expect(game.canPutSub(3,-1,3,1)).toBeFalsy()
      expect(game.canPutSub(3,-1,1,-1)).toBeFalsy()
    }) 

総当たりでcanPut関数を実行してみます。
再帰関数のテストは削除するかonlyを外しましょう

    it.only('canPut関数テスト', () => { 
      const game = new Game() 
      for( let x = 0; x <= 7; x++ ){ 
        for( let y = 0; y <= 7; y++ ){ 
          console.log( 'x:' + x + ' y:' + y + ' put?:' + game.canPut(x,y)) 
        } 
      } 
    }) 

trueの個数が多いです。
本来であれば4-2,2-4,5-3,3-5の四つのみが置けるはずです。
1,1に対して全方向の再帰関数を実行してみましょう。

    it('再帰関数テスト', () => { 
      const game = new Game() 
      console.log(board.canPutSub(1,0,2,1)) 
      console.log(board.canPutSub(1,0,0,-1)) 
      console.log(board.canPutSub(2,1,1,0)) 
      console.log(board.canPutSub(0,-1,1,0)) 
      console.log(board.canPutSub(2,1,2,1)) 
      console.log(board.canPutSub(2,1,0,-1)) 
      console.log(board.canPutSub(0,-1,2,1)) 
      console.log(board.canPutSub(0,-1,0,-1)) 
    }) 

斜めだけTrueになっているようです。
ということはどこかで+-を間違っている?

見つけました。相手色でなければという表現が悪かったのかもしれません。

        //接してる石が相手色でなければ次へ 
        if ( this.board[x+di_x][y+di_y] === -this.player ) 
continue

ここは分かりやすくゲッターを定義してみましょう

  get enemy(){
    return -this.player
  }
        //接してる石が相手色でなければ次へ
        if ( this.board[x+di_x][y+di_y] != this.enemy ) continue

少し分かりやすくなったのではないでしょうか?
ログを表示していた部分はskipに設定し他のテストを実行します。
5passedでエラーはなくなるになるはずです。

石を裏返す

次は石を裏返す処理を実装します。
テストは2行追加しましょう

    it('異色を挟んで反対側に同色がある場合石が置け、ターンが切り替わる', () => { 
      const board = new Board() 
      board.put(4,2) 
      expect(board.board[4][2]).toBe(1) 
      expect(board.board[4][3]).toBe(1) 
      board.put(5,4) 
      expect(board.board[5][4]).toBe(-1) 
      expect(board.board[5][4]).toBe(-1) 
    }) 

実装は このようになります。

  turnDiscs(x,y) {
    if( this.board[x][y] != 0 ) return false
    for( let di_x = -1 ; di_x <= 1 ; di_x++ ){
      for( let di_y = -1 ; di_y <= 1 ; di_y++ ){
        //中心以外の8方向をチェックする
        if ( di_x === 0 && di_y === 0 ) continue
        //盤外の場合は次へ
        if ( x+di_x < 0 || y+di_y < 0 || 7 < x+di_x || 7 < y+di_y ) continue
        //接してる石が相手色でなければ次へ
        if ( this.board[x+di_x][y+di_y] != this.enemy ) continue
        //再帰チェック
        if( this.canPutSub( x+di_x, di_x, y+di_y, di_y ) ){
          this.turnDiscsSub( x, di_x, y, di_y )
        }
      }
    }
    return false
  }
  turnDiscsSub( x, di_x, y, di_y ) {
    //自色があればtrueを返す
    if ( this.board[x+di_x][y+di_y] === this.player ) return true
    this.board[x+di_x][y+di_y] = this.player
    //次の石のチェックへ
    return this.turnDiscsSub( x+di_x, di_x, y+di_y, di_y ) 
  }

テストは通りましたが配列の中身がどのようになっているのか気になります。次の文で表示してみましょう。

      console.log(board.board.map(x=>x.map(y=>y==-1?2:y).join('') ) )

mapは配列内部全てに処理をする関数です。二重にして二次元配列全てに処理をしています。
さらにアロー演算子と三項演算子を併用して-1を2に置き換えることで出力時のマス目ずれないようにしています。
joinは配列を一つにすることで無駄な改行が入らないようにしています。

マウス処理

オセロのルールは一部実装されましたがまだクリックしても石が置けません。マウス処理を追加しましょう。
addEventListenerでマウスをクリックし離した時点で処理を実行されるようにします。

renderer/reversi.js

import Game from '../renderer/game' ;
import Draw from '../renderer/draw'
var context = document.getElementById("canv").getContext('2d');

context.canvas.addEventListener('mouseup', ev_mouseClick)
var game = new Game()
var draw = new Draw(context)
draw.draw_discs(game.board)

function ev_mouseClick(e) {
  let x = Math.floor((e.clientX-e.target.getBoundingClientRect().top)/60)
  let y = Math.floor((e.clientY-e.target.getBoundingClientRect().left)/60)
  game.put(x,y)
  draw.draw_board()
  draw.draw_discs(game.board)
}

置けるようになりましたか?
ここまでくればなんとか人対人での勝負はできます。

TurnEnd

何度か打ってみると気づきますが、PASS機能がなく途中で打てなくなってしまうことがあります。また、両プレイヤーが置けない状態となったらその時点で勝敗が決しますが終わりもありません。
これらのルールを実装しましょう。
以下のサイトを参考にテストを作成しましょう。
オセロ豆知識

renderer.test.js

  describe('pass', () => {
    it('最速PASS手順', () => {
      const game = new Game()
      game.put(4,5)
      game.put(5,5)
      game.put(2,3)
      game.put(4,6)
      game.put(4,7)
      game.put(3,7)
      game.put(6,5)
      const def_player = game.player
      game.put(5,7)
      //PASSされて同じプレイヤーに戻ること
      expect(def_player).toBe(game.player)
      console.log(game.board.map(x=>x.map(y=>y==-1?2:y).join('') ) )
    })
    it('白全滅' ,() => {
      const game = new Game()
      game.put(4,5)
      game.put(5,3)
      game.put(4,2)
      game.put(3,5)
      game.put(2,4)
      game.put(5,5)
      game.put(4,6)
      game.put(5,4)
      game.put(6,4)
      //白が全滅していることを確認
      expect(game.disc_count(-1)).toBe(0)
      //試合が終了するとturnEndがTrueを返す
      expect(game.turnEnd()).toBeTruthy()
    })
    it('黒全滅' ,() => {
      const game = new Game()
      game.put(4,5)
      game.put(5,5)
      game.put(5,4)
      game.put(3,5)
      game.put(2,4)
      game.put(1,3)
      game.put(2,3)
      game.put(5,3)
      game.put(3,2)
      game.put(3,1)
      //黒が全滅していることを確認
      expect(game.disc_count(1)).toBe(0)
      //試合が終了するとturnEndがTrueを返す
      expect(game.turnEnd()).toBeTruthy()
    })
  })

PASSを実装するにはプレイヤーが置けるかどうか判断する必要があります。
まずは盤面における場所があるか確認する処理を追加しましょう。

game.js

  canPutChecker(){
    let canPutBoard = [
      [0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0]
    ]
    for( let x = 0; x <= 7; x++ ){
      for( let y = 0; y <= 7; y++ ){
        canPutBoard[x][y] = this.canPut(x,y)
      }
    }
    return canPutBoard
  }

関数の確認のためにテストを書きます。
この辺りはテストがしづらくなるため一度consoleログで確認し問題がなければ比較式で書き直すという方法で作成しています。二つ目のチェックを比較式に直してみてください。

renderer.test.js

    it('チェックXチェック' ,() => {
      const game = new Game()
      expect(game.canPutChecker().map(x=>x.map(y=>y==true?1:0) ) ).toStrictEqual(
        [
          [0,0,0,0,0,0,0,0],
          [0,0,0,0,0,0,0,0],
          [0,0,0,1,0,0,0,0],
          [0,0,1,0,0,0,0,0],
          [0,0,0,0,0,1,0,0],
          [0,0,0,0,1,0,0,0],
          [0,0,0,0,0,0,0,0],
          [0,0,0,0,0,0,0,0]
        ]
      )
      game.put(4,5)
      console.log(game.canPutChecker().map(x=>x.map(y=>y==true?1:0).join(',') ) )
    })

置けるかどうかのチェックが出来るようになったのでTurnEnd関数を作成します。

game.js

  disc_count(player){
    let count=0
    //配列全てをサマリして置ける箇所数を出す
    this.board.forEach(x=>x.forEach(y=>y==player?count++:null))
    return count
  }
  turnEnd(){
    let sum=0
    //配列全てをサマリして置ける箇所数を出す
    this.canPutChecker().forEach(x=>x.forEach(y=>sum+=y))
    if( sum == 0 ){
      //プレイヤーをPASS
      this.player = -this.player
      //配列全てをサマリして置ける箇所数を出す
      sum = 0
      this.canPutChecker().forEach(x=>x.forEach(y=>sum+=y))
      if( sum == 0){
        return true
      }
    }
    return false
  }

ここでテストを実行し問題ないことを確認してください。
あとはこれらを画面に反映させます。

reversi.js

import Game from '../renderer/game' ;
import Draw from '../renderer/draw' ;
var context = document.getElementById("canv").getContext('2d');

context.canvas.addEventListener('mouseup', ev_mouseClick)
var game = new Game()
var draw = new Draw(context)
draw.draw_discs(game.board)

function ev_mouseClick(e) {
  let x = Math.floor((e.clientX-e.target.getBoundingClientRect().top)/60)
  let y = Math.floor((e.clientY-e.target.getBoundingClientRect().left)/60)
  game.put(x,y)
  draw.draw_board()
  draw.draw_discs(game.board)
  if(game.turnEnd()){
    alert(
      game.disc_count(1)>game.disc_count(-1)?"黒の勝利です":
      game.disc_count(1)<game.disc_count(-1)?"白の勝利です":
      "ドローです"
    )
    game = new Game()
    draw.draw_board()
    draw.draw_discs(game.board)
  }
}

これで実装が終わりと行きたいところですが、実際に起動してみると最後の石を置いた時点でアラートが出てしまい石が置かれず勝敗が出てしまいます。
alert処理はキャンバスのリフレッシュをブロックしてしまうためでこれを防ぐためにalert処理を非同期処理にします。
canvas要素の基本的な使い方まとめ

reversi.js

    setTimeout(()=>{
      alert(
        game.disc_count(1)>game.disc_count(-1)?"黒の勝利です":
        game.disc_count(1)<game.disc_count(-1)?"白の勝利です":
        "ドローです"
      )
      game = new Game()
      draw.draw_board()
      draw.draw_discs(game.board)
    },0)

setTimeoutを使ってアラートも非同期処理としてみました。
こちらの方法でも問題なく動作するかと思います。
しかし、こちらの書き方ではalert処理の再利用が考えられておらず後続処理の実行タイミングも明示的ではありません。
遅延実行する形に変更します。

reversi.js

function ev_mouseClick(e) {
  let x = Math.floor((e.clientX-e.target.getBoundingClientRect().top)/60)
  let y = Math.floor((e.clientY-e.target.getBoundingClientRect().left)/60)
  //alertを非同期としてPromiseでラップする
  //resolveはthenメソッドに渡された処理を実行する
  let alertWithNoBlock = msg => new Promise(
    (resolve, reject) => setTimeout(() => resolve(alert(msg)), 0));
  game.put(x,y)
  draw.draw_board()
  draw.draw_discs(game.board)
  if(game.turnEnd()){
    alertWithNoBlock(
      game.disc_count(1)>game.disc_count(-1)?"黒の勝利です":
      game.disc_count(1)<game.disc_count(-1)?"白の勝利です":
      "ドローです"
    ).then(result => {
      game = new Game()
      draw.draw_board()
      draw.draw_discs(game.board)
    });
  }
}

これで基本的な実装に関しては終了です!
交互に打てば最後の勝利判定までしてくれます。

次の章では見た目とAI機能の実装を行います!
置ける場所を光らせたり、今どちらのプレイヤーか表示したりと便利機能を実装していきます。

WebStormでChrome attach debug

$
0
0

English

vscode-chrome-debug - Attachと同じことをWebStormでやる方法です。公式ドキュメント含め誰も書いてないっぽいのでメモ。

vscode-chrome-debug - Attachの方法でChromeを起動して、Port 9229でChrome Debugging protocolをlistenさせます。Linuxの場合は google-chrome --remote-debugging-port=9229実行後、以下のコマンドでちゃんとlistenしているか確認。

$ netstat -an | grep 9229
tcp        0      0 127.0.0.1:9229          0.0.0.0:*               LISTEN

$ lsof -i :9229
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
chrome  18347  wsh  132u  IPv4 729759      0t0  TCP localhost:9229 (LISTEN)

WebStormでConfiguration "Attach to Node.js/Chrome" を作成:
image.png

作成したConfigurationでDebugを開始すると、Chromeで開いているタブ一覧が表示されるので、デバッグしたいタブを選びます。今回はFirebaseチュートリアルのFriendly Chat
image.png

無事ブレークポイントにヒットして人権を手にしました。
image.png

JavaScript基礎:配列のよく使うメソッド

$
0
0

はじめに

配列の操作は普段よく使うので、関連メソッドをまとめてみます。

配列の重複値を削除

new setで重複値取り除く

constdata=["four","one","two","three","one"]constnewData=newSet(data)console.log(newData)

image.png

配列の値置換

splice() メソッドは、 既存の要素を取り除いたり、置き換えたり、新しい要素を追加したりすることで、配列の内容を変更します。

constdata=["four","one","two","three","one"]data.splice(1,1,'one1','one2')console.log(data)

image.png

map、fromメソッドで配列値加工

fromメソッド

Array.from() メソッドは、配列風オブジェクトや反復可能オブジェクトから、新しい、浅いコピーの Array インスタンスを生成します。

constdata=["1","2","3"]console.log(Array.from(data,x=>x+x));

Array ["11", "22", "33"]

mapメソッド

vararr=[1,3,6,9];constmap=arr.map(x=>x**2);console.log(map);

Array [1, 9, 36, 81]

配列クリア

vararr=[1,3,6,9];// 長さを0でクリアarr.length=0// 空の配列でリセット//arr = []console.log(arr);

Array []

配列をオブジェクトに変換

vararr=["one","two","three"];varobj={...arr}console.log(obj);

Object { 0: "one", 1: "two", 2: "three" }

配列に初期値をfill

vararr=newArray(8).fill(100)console.log(arr)

Array [100, 100, 100, 100, 100, 100, 100, 100]

配列のマージ

constarr1=newArray(8).fill(100)constarr2=newArray(8).fill(200)constarr3=[...arr1,...arr2]console.log(arr3)

Array [100, 100, 100, 100, 100, 100, 100, 100, 200, 200, 200, 200, 200, 200, 200, 200]

concatも配列マージできるメソッドです。
concat() メソッドは、配列に他の配列や値をつないでできた新しい配列を返します。

スプレッド構文

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax

配列の反転

constarray1=['one','two','three'];constreversed=array1.reverse();console.log('reversed: ',reversed);

"reversed: " Array ["three", "two", "one"]

reduceメソッド

reduce() は配列の各要素に対して(引数で与えられた)reducer 関数を実行して、単一の値にします。

constarray=[1,2,3,4];consttotal=array.reduce((total,currentValue)=>total+currentValue)console.log(total)

10

filterメソッド

filter() メソッドは、引数として与えられたテスト関数を各配列要素に対して実行し、それに合格したすべての配列要素からなる新しい配列を生成します。

constnums=[10,15,20,25];constoddNums=nums.filter(num=>num%2==1)console.log(oddNums)

Array [15, 25]

forEach

for句の代わりとしてよく使います。

constarray=['one','two','three'];array.forEach(function(element){console.log(element);});

"one"
"two"
"three"

sort

// 文字列で昇順にするconstdata=['1','2','10','20'];data.sort();console.log(data);// 数字で昇順にするconstdata2=[1,2,10,20];data2.sort();console.log(data2);// 自分の比較メソッドで降順にするconstdata3=[1,2,10,20];data3.sort(function(a,b){if(a<b){return1;}elseif(a>b){return-1;}else{return0;}});console.log(data3);

Array ["1", "10", "2", "20"]
Array [1, 10, 2, 20]
Array [20, 10, 2, 1]

ほかもいろんなメソッドがあります。
参考URL
Map: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map
Arraya: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array
Set: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Set

以上

初心者がJSDocを調べてみた

$
0
0

免責事項

この記事は初心者視点でザックリとした説明をしています。正確性に欠ける可能性がございますが、ご了承ください。「明らかに違うよ」ということがありましたら、ご指摘くださると幸いです。

環境

OS:最新版ではないMacOS

目次

  1. JSDocとは
  2. JSDocの書き方

1. JSDocとは

JSDocは、JavaScriptのソースコードにアノテーション(注釈)を追加するために使われるマークアップ言語です。

なぜ使うか

複数人で開発を進める場合や、大規模なプログラムを開発する場合に、
変数のデータ型やオブジェクトの種類(配列、関数、コンストラクタ、クラスなど)をコメントとして記述することで、他の人がそれらを見分けることができ、開発の効率が上がります。

加えて、JSDocに対応しているエディターを使うとかなり開発効率が上がると思います。
JSDocに対応しているエディターVSCodeで、以下のように関数を作りJSDocをコメントとして書くと、
スクリーンショット 2019-11-04 21.25.51.png

以下のように別のファイルで関数にカーソルを合わせたときに、JSDocコメントが表示されます。
スクリーンショット 2019-11-04 21.25.03.png

完璧な記述をする必要はありませんが、できるだけ推奨されている書き方で記述すると良いそうです。

2. JSDocの書き方

"/**"で初めて、"*/"で終わります。
その間に記述がある分だけ"*"を書きましょう。

/**
 * [記述]
 */

変数

@typeの後の{}内に変数の型(String, Number, Boolean)を書き、そのあとに変数の説明を書きます。

/**
 * @type {Number} 年齢
 */letage=23;

配列

@typeの後の{}内にArrayと書き、そのあとに配列の説明を書きます。

/**
 * @type {Array} 年齢の配列
 */letageArray=[10,22,30];

連想配列

最初に@typeで連想配列の説明を書きます。連想配列はオブジェクトの扱いなので、{}の中はobjectと書きます。
次に連想配列のオブジェクト内の@typeで連想配列のペアが何を意味しているのかを書きます。{}内は連想配列の値(value)の型を書きます。

/**
 * @type {Object} 会員情報の連想配列です
 */constmemberInfo={/**
   * @type {Number} 会員番号
   */"id":1,/**
   * @type {String} 会員名
   */"name":"taro imo"}

クラス(インスタンス)

はじめにクラス(インスタンス)の説明をします。
@constructorがコンストラクタであることを表します。(なくても良い)
@thisが何を指しているのか{}の中に書きます。
@paramで関数の引数の説明を書きます。{}の中には関数の引数の型を書きます。

/**
 * Personクラスのインスタンスを作成する。
 * @constructor
 * @this {Person}
 * @param {String} name 名前
 * @param {Number} age 年齢
 */letPerson=function(name,age){this.name=name;this.age=age;}lettaroImo=newPerson('taroImo',23);console.log(taroImo.name);// taroImoconsole.log(taroImo.age);// 23

関数

まずコメントの最初に関数の説明を書きます。
次に@paramで関数の引数の説明を書きます。{}の中には関数の引数の型を書きます。
最後に@returnで返り値の説明を書きます。{}の中には返り値の型を書きます。
*関数内の変数のJSDocは省いています。

/**
 * 10年後の年齢を返す
 * @param {Number} age 年齢
 * @return {Number} 10年後の年齢
 */functiontenYearsLater(age){lettenYearsLater=age+10;returntenYearsLater;}

おわりに

この書き方が公式というわけではなく色々な書き方があり、人・企業・組織により書き方は違います。

参考

「N予備校 プログラミングコース」
https://www.nnn.ed.nico/
「Wikipedia JSDoc」
https://ja.wikipedia.org/wiki/JSDoc
「JSDocでJavaScript のコメントを書こう」
https://sterfield.co.jp/designer/jsdoc-で-javascript-のコメントを書こう/
「Google JavaScript Style Guide」
https://google.github.io/styleguide/javascriptguide.xml?showone=Comments#Comments

もしも npm script の引数にブランチ名をいれてみたくなったら

$
0
0

git のローカルブランチをとるのは意外にめんどくさい。

考えに考えられている git なら簡単にとれそうなものなんですが、素の git から取り出すのは結構めんどくさいようです。

https://qiita.com/sugyan/items/83e060e895fa8ef2038c

正直もっと簡単なコマンド一発で取れるかなと思っていたのですが。
もっと簡潔な方法あったら教えてください。

追記:教えてもらいました。

git rev-parse --abbrev-ref @ 

きっと誰かが作ってくれているはず

npm 検索すると出てきました。世界は支え合ってます。

git-branch

npm の引数に組み込むには?

npm script に色々書いても、Windows では実行できなかったりするので、node.js を作りました。

引数に callback に入ってくるブランチ名を渡しています。

/* eslint-disable */constbranch=require('git-branch');const{exec}=require('child_process');branch('./').then((br)=>{exec(`npx vue-cli-service storybook:build -c config/storybook -o test-sb-${br}`,function(error,stdout,stderr){if(stdout){console.log('stdout: '+stdout);}if(stderr){console.error('stderr: '+stderr);}if(error!==null){console.log('Exec error: '+error);}})}).catch(console.error);

ここでは、storybook の出力にブランチ名を入れてみました。

npm scripts として組み込む

  "scripts": {
    "sb:td": "node ./sb-now-deploy.js"
  },

こんな感じで書いてあげると npm スクリプトのように実行できます。
shell も使ってないので、Windows 環境でも使えると思われます。

以上でーす👋👋👋

CircleCI で Node.js のバージョンが合わない(2019年11月)

$
0
0

なにがおきた

CircleCIのruby:2.6.5-node-browsersを使ったジョブでこんなエラーが出た。

yarn install v1.19.1
[1/5] Validating package.json...
error hogehoge@0.1.0: The engine "node" is incompatible with this module. Expected version "~10.16.0". Got "12.13.0"
error Found incompatible module.

package.jsonでは 10.16.0が指定されてるけど 12.13.0が使われてるとのこと。

原因

さっき(2019-11-07現在) CircleCIのdockerイメージの Node のバージョンが軒並み上げられた。この更新で-nodeとかついてるイメージのバージョンが 10.16.3 -> 12.13.0になった。10月21日に12系がLTSになったかららしい。(Convenience Image Node Variants will be updated to v12.x on November 4th - CircleCI Discuss)

対策

12.13.0にしちまえ!!!!

package.json
   "engines": {
-    "node": "~10.16.0"
+    "node": "~12.13.0"
   },

もしくは、うまくいってた時のdocker image id を指定する

のっぴきならない理由で 10.16のままにする場合、ベストプラクティス - Pre-Built CircleCI Docker Imagesにある方法でうまくいった。

また、使用するイメージを特定の SHA に至るまで指定することができます。 これにより、変更が加えられるまでの間、特定のイメージをテストすることができます。

使用するイメージを細かく指定するには、以下の 2つの方法があります。

  • タグを使用してイメージのバージョンや OS を指定する
  • Docker イメージ ID を使用してバージョンを指定する

うまくいってた時のCircleCIのジョブのログを確認するとその時に使われてたDockerイメージIDが出力されてるのでそれをコピって指定したら古いのが使われた。以下は ruby:2.6.5-node-browsersの例。しばらくこれでしのいでおいて、その間に12系を使えるようにするのがよさそう。

Dockerhub でタグの履歴の追い方がわからなかった。わかったら追記する。

.circleci/config.yml
     docker:
-      - image: circleci/ruby:2.6.5-node-browsers
+      - image: circleci/ruby@sha256:7b5eede6785426c86fd08328a2b98b54b7bf67cdbd7d579c9b792e11f6bea8ed

もしくは、自分でdockerfile書く。


Node.js (2)データ処理とモジュール化

$
0
0

node.jsのデータ 処理はchrome中にconsoleの機能似てる。

データ処理

先ずはconsole.logに文字列入れる。

app.js
console.log('Hello World');

以下はnodeの実行結果。

terminal
$ node app.js
Hello World

つぎは簡単な変数計算を試しよう。

app.js
vara=100;varb=200;varc=a+b;console.log(c);
terminal
$ node app.js
300

モジュール化

複数ファイル間のデータ輸出と輸入につて一般的な方法は三つがある。先ずは二つファイルを作る、app.jsの方はデータ受ける用、data.jsはデータ輸出用。app.js中身のrequire関数は輸出ファイルのバス引数にようるとデータを受け取れる、data.jsはexports函数形、exports変数形とmodule形をまとめる。

app.js
varcontent=require('./data');console.log(content.apple());
data.js
//exports函数形exports.apple=function(){return'apple!!';}
terminal
$ node app.js
apple!!

次はexports変数形、変数見たいdataで保存する輸出形だ。

app.js
varcontent=require('./data');console.log(content.data);
data.js
//exports変数形exports.data=100;
terminal
$ node app.js
100

最後はmodule形を試しってmodule.exports中身に保存するデータ輸出形だ。

app.js
varcontent=require('./data');console.log(content);
data.js
//module形module.exports={price:3000}
terminal
$ node app.js
3000

注意点

同時に使うは行けません!!
```app.js
var content = require('./data');

console.log(content);
console.log(content.data);
console.log(content.apple());
```

data.js
//module形exports.data=100;exports.apple=function(){return'apple!!';}module.exports={price:3000}
terminal
$ node app.js
{ price: 3000 }
undefined
console.log(content.apple());                    ^

TypeError: content.apple is not a function
    at Object.<anonymous>    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)
    at startup (internal/bootstrap/node.js:285:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:739:3)

Node.js express-validatorを使ってみた

$
0
0

expressフレームワークでexpress-validatorを使ってみたので、バリデーションする方法をまとめてみました。

環境

  • MacOSX (10.14)
  • Node.js (v10.15.2)
  • npm (6.4.1)
  • express (4.16.0)
  • express-validator (6.2.0)

準備

まずexpress-validatorをインストールします
npm install express-validator

express-validator概要

できること
+ バリデーション
+ カスタムバリデーション
+ サニタイズ
+ カスタムサニタイズ
+ カスタムエラーメッセージ

バリデーションサンプルコード(ノーマルな使用方法)

まず一番基本的な使い方は以下になります.(公式ドキュメントからコード抜粋)

app.js
const{check,validationResult}=require('express-validator');// 第二引数にバリデーションの記述app.post('/user',[// checkメソッドを使用してバリデーションを実行check('username').isEmail(),check('password').isLength({min:5})],(req,res)=>{// バリデーションの結果にエラーがあるかのチェックconsterrors=validationResult(req);if(!errors.isEmpty()){returnres.status(422).json({errors:errors.array()});}User.create({username:req.body.username,password:req.body.password}).then(user=>res.json(user));});
  1. ルーティングメソッド(今回はpostファンクション)の第2引数に配列でバリデーションの記述をする。
  2. checkファンクションは全てのreqインスタンスの値から指定したフィールド名に一致するものをバリデーションします。
    bodyの値を対象にしたいならbodyファンクションをクエリパラメータならqueryファンクションを使います。そのほかに種類はあるので公式ドキュメントを見てください(公式ドキュメント)
  3. .isEmail()ファンクションで値がメールアドレスなのかをチェックしています。そのほかにもチェックするためのファンクションがここに記載されているので、見てください。
  4. 第3引数のコールバック関数内にvalidationResultから値を取得し、エラーがあるのかをチェックします。
    エラーがあった場合はHTTPステータスコード422を指定して、レスポンスを返します。

以上がノーマルなバリデーションでの使用方法です。
ただノーマルな使用方法ではルーティングの記述が読みずらくなってしまいます。
そこで次の使用方法が私のおすすめです。

外部ファイル化してすっきりと

ItemRegistValidator.js
const{check}=require('express-validator');module.exports=[check('itemId').not().isEmpty().withMessage('必須項目です。').isInt().withMessage('アイテムIDは数値を入力してください'),check('itemName').not().isEmpty().withMessage('必須項目です。'),];
app.js
const{validationResult}=require('express-validator');// 外部ファイル化したバリデーション読み込みconstItemRegistValidator=require('../midleware/validators/itemRegistValidator');// 第2引数で、バリデーションを実行router.post('/',ItemRegistValidator,(req,res)=>{// バリデーションの結果にエラーがあるかのチェックconsterrors=validationResult(req);if(!errors.isEmpty()){returnres.status(422).json({errors:errors.array()});}Item.create({itemId:req.body.itemId,itemName:req.body.itemName}).then(item=>res.json(item));});

バリデーションの記述を外にすることで、だいぶすっきりしました。
バリデーション対象パラメータが多ければ多いほど、外部ファイルにするこの方法がおすすめです。

  1. withMessage()ファンクションはエラーメッセージを任意の内容に指定することができます。
  2. その他に関してはノーマルな使用方法と同様です。'ItemRegistValidator.js'にて配列で、バリデーション処理の記述をし、ルーティングメソッドの第2引数で呼び出します。

バリデーションをカスタマイズ

// base64でエンコードされた画像ファイルの拡張子をバリデーションcheck('image').custom(image=>{letextension=image.slice(image.indexOf('/')+1,image.indexOf(';'));if(extension!='png'&&extension!='jpeg'){thrownewError('画像の拡張子はpngまたはjpegを使用してください');}returntrue;})

validation.jsに用意されていないバリデーションをしたい時customファンクションを使いましょう。
1. customファンクションに引数でコールバック関数を指定し、バリデーションの処理をそのコールバック関数に記述します。
2. エラーであればErrorインスタンスをthrowし、問題なければtrueを返します。

所感

express-validationを使用の仕方を記述いたしました。
上記の内容で、バリデーション処理を記述できるかと思います。
ここには書いていない点もありますので、公式ドキュメントを見てください。
便利な関数が揃っているため非常に書きやすいと思います。

Node.jsでキー入力を検知する

$
0
0

概要

Node.jsアプリケーションを単体で動かしている時に、押されたキー情報を取得する方法

背景

Node.jsアプリケーションを単体で動かしている場合(コンソールから制御)において、キーの押下をトリガーにキーの状態そのものを取得する方法として, readlineがFirebaseと一緒に使えなかったり、ffiパッケージが入らなかったため。

実装手順

1.npm install keypressでパッケージを導入

今回は keypress - npmを利用します。
ターミナルで下記コマンドを入力してください。

npm install keypress

2. 使ってみる

以外、殆どが公式npmページからのコピペですが、少し補足をしています。

varkeypress=require('keypress');// make `process.stdin` begin emitting "keypress" eventskeypress(process.stdin);// listen for the "keypress" eventprocess.stdin.on('keypress',function(ch,key){console.log('got "keypress"',key); //Ctrl + c入力のときの処理if(key&&key.ctrl&&key.name=='c'){process.stdin.pause();//強制終了するなら process.stdin.exit();}});process.stdin.setRawMode(true);process.stdin.resume();

ご覧の通り、key.nameでキーの名前の取得が出来たり,key.”any key”(上の例だとkey.ctrl)でctrlやshiftが押されているかどうかの判定もできるようです。

最後に

Node.jsの経験がほとんどなく、なんならサーバーサイドの経験がnullに近い状態だったので色々大変でした。
でもこうして触っていると色々できるなぁと思うこともあり、ニコ生コメでLチカさせてみたり、ニコ生コメントではちゅねにネギを振らせてみたりして遊べて色々楽しいなとか思い始めました。今後、もう少し本格的にサーバーサイドもやってみたいなと思います。

もし、不備、誤記、もっといい方法があるよ! などございましたらお気軽にお申し付けください。

参考文献

keypress - npm

package.jsonのversionをコマンドで取り出したい

$
0
0

PWAとかやってると、index.htmlのmetaタグにversionを記載したかったりするはずです:santa:
その際に、適当にバージョンふるのもいいですが、自分はpackage.jsonのバージョンで運用していて、かなり簡単にバージョン取り出せたので、そのコマンドを共有します:fish:

TL;DR

  • package.jsonのバージョンを取得
  • npxコマンド使用
  • package.jsonのパースとかしない、コマンドのみ
  • シェル上で取り出す(CIとかで使用)

環境

  • npm 5.2.0以上だそうです(npx使用するので)

【結論】version取得コマンド

npx -c'echo "$npm_package_version"'# 1.0.0

これで取得した値でindex.htmlを上書きできますね:santa:

npxコマンドとは

この記事を参考にしていただくと早いのですが、簡単にいうとローカルのパッケージを使用するコマンドです:fish:

npxを使用すると、

(node_modules/.bin/eslint)

or

$(npm bin)/eslint

npx eslint

で短いコマンドで実行できますね:santa:

controller内などjavascriptで取得したい

普通にrequireで取り出せますね:santa:

constpackageJson=require("package.json")constversion=packageJson.version

おしまい:santa:

参考

nodeでnpmのバージョンが違うと怒られた

$
0
0

現象

npm installしようとしたらこんなエラーが出て怒られた

$ npm install
WARNING: You are likely using a version of node-tar or npm that is incompatible with this version of Node.js.
Please use either the version of npm that is bundled with Node.js, or a version of npm (> 5.5.1 or < 5.4.0) or node-tar (> 4.0.1) that is compatible with Node.js 9 and above.
npm[14907]: ../src/node_zlib.cc:551:static void node::(anonymous namespace)::ZlibStream::Init(const FunctionCallbackInfo<v8::Value> &): Assertion `args.Length() == 7 && "init(windowBits, level, memLevel, strategy, writeResult, writeCallback," " dictionary)"' failed.
 1: 0x10003cf99 node::Abort() [/usr/local/bin/node]
 2: 0x10003bfbb node::AddEnvironmentCleanupHook(v8::Isolate*, void (*)(void*), void*) [/usr/local/bin/node]
 3: 0x1000da0e2 node::(anonymous namespace)::ZlibStream::Init(v8::FunctionCallbackInfo<v8::Value> const&) [/usr/local/bin/node]
 4: 0x100242aaf v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo*) [/usr/local/bin/node]
 5: 0x100241ff1 v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) [/usr/local/bin/node]
 6: 0x100241690 v8::internal::Builtin_Impl_HandleApiCall(v8::internal::BuiltinArguments, v8::internal::Isolate*) [/usr/local/bin/node]
 7: 0x1c96c9adbe3d 
Abort trap: 6

でもnpmのバージョンは> 5.5.1になってる

$ npm -v
6.12.0

解決法

nodejsをインストールし直した。
バージョン管理ツール"n"を使ってたので、n上で削除→インストールした

$ n prune
$ n install lts

そうしたら無事にnpm installできた

色々なツールのHTTPSリクエストを覗き見る方法メモ

$
0
0

HTTPSで通信する色々なコマンドにMITMするためのパラメータの渡し方が微妙に違っていて毎回調べている気がするのでこっちに証明書ファイルとproxyのURLの渡し方をメモしていきます。
デバッグ用途で使う想定。

前準備

pemファイル作る

DER -> PEMの変換

openssl x509 -inform der -in cert.crt -out cert.pem

proxy立てる

立てるだけ。ここでは127.0.0.1:8080に立ってる想定で話をします。

Ruby

v2.6 net/httpsでためした

$ SSL_CERT_FILE=./cert.pem HTTP_PROXY="http://127.0.0.1:8080"

gcloudコマンド

Google Cloud SDK 269.0.0でためした
gcloudコマンドはHTTP_PROXYとHTTPS_PROXY環境変数を受け付けてそう。
configで証明書指定するとうっかりunsetし忘れそうなので誰か環境変数でセットする方法しってたら教えて下さい。

$ gcloud config set core/custom_ca_certs_file ./cert.pem
$ HTTPS_PROXY="http://localhost:8080" gcloud foo bar
$ gcloud config unset core/custom_ca_certs_file

Node.js

v12.9.0でためした

$ CUSTOM_CA_CERTS_FILE=./cert.pem  HTTP_PROXY="http://localhost:8080" node ./main.js

『自分用』テンプレートエンジンを使って簡単なアンケートをつくってみた

$
0
0

免責事項

この記事は初心者視点でザックリとした説明をしています。正確性に欠ける可能性がございますが、ご了承ください。「明らかに違うよ」ということがありましたら、ご指摘くださると幸いです。

環境

OS:最新版ではないMacOS
VirtualBox:5.2.26
Vagrant:2.2.6
Ubuntu:ubuntu/bionic64 v20181129.0.0

目次

  1. テンプレートエンジンとは
  2. アンケートをつくる

1. テンプレートエンジンとは

テンプレートエンジンはテンプレートと呼ばれる雛形とデータを合成し、HTML等の成果ドキュメントを出力するライブラリです。

Wikipediaにわかりやすい画像があったのでお借りしました。
320px-TempEngGen015.svg.png
(画像 by Dreftymac at English Wikipedia)

ようするに、HTMLなどのドキュメントを作るのを簡単にしてくれるらしいです。

今回はPugというNode.jsのテンプレートを使って簡単なアンケートを作ります。
Pugは以下のように書きます。

Pug
doctype html
html(lang="ja")
  head
    meta(charset="UTF-8")
    title 会員登録ページ
  body
    h1 会員情報
    form(method="post" action="/member/registry")
    メールアドレス: <input type="text" name="mail">
      名前: <input type="text" name="name">
      input(type="radio" name="性別" value="男")
      span 男
      input(type="radio" name="性別" value="女")
      span 女
      button(type="submit") 登録

以下はHTMLです。

HTML
<!DOCTYPE html><htmllang="ja"><head><metacharset="UTF-8"><title>会員登録ページ</title></head><body><h1>会員情報</h1><formmethod="post"action="/member/registry">メールアドレス: <inputtype="text"name="mail">名前: <inputtype="text"name="name"><inputtype="radio"name="性別"value="男"/><inputtype="radio"name="性別"value="女"/><buttontype="submit">登録</button></form></body></html>

見比べてみると、Pugは閉じタグが不要なため簡潔に表現することができます。
閉じタグがないため、改行やインデントなどで全体を構成していきます。

慣れるまで違和感がありますが、慣れてくると以下のようなメリットがあります。
・CSSと書き方の統一(classは. 、idは#)
・ファイル分割ができるため、管理がしやすい。
・繰り返しが楽につくれる

この他にもたくさんのメリットがあるので、Node.jsを使う方にはおすすめのテンプレートエンジンです。

2. アンケートをつくる

Pugは先ほど見た通り、
・要素名(h1やbuttonなど)の後に要素の値を記述します。
・属性(methodやtype)を書く場合は要素名のあとに () を書き、その中に属性を記述していきます。
・属性が2つ以上続く場合には半角スペースで区切ります。
・入れ子構造にする場合はインデントを使います。
というルールがあります。

form.pug
doctype html
html(lang="ja")
  head
    meta(charset="UTF-8")
    title アンケート
  body
    h1 どのスポーツがやりたいですか?
    form(method="post" action=path)
      span 名前:
      input(type="text" name="name")
      span 年齢:
      input(type="text" name="age")
      span 血液型:
      select(name="blood")
        option(value="A") A型
        option(value="B") B型
        option(value="O") C型
        option(value="AB") D型
      input(type="radio" name="favoriteSport" value=firstItem)
      span #{firstItem}
      input(type="radio" name="favoriteSport" value=secondItem)
      span #{secondItem}
      button(type="submit") 投稿

以上の.pugファイルをつくり、ファイルが保存されているディレクトリに移動し、
コンソールで以下のようにコマンドを打ちコンパイルをしてみます。
今回は表示の確認のためにコンパイルするだけです。

$ pug form.pug --pretty

これでform.htmlファイルを作り出すことができます。
--prettyオプションは、htmlを綺麗な形で出力してくれます。
ディレクトリを見てみて、以下のようなhtmlファイルができていたら確認成功です。

form.html
<!DOCTYPE html><htmllang="ja"><head><metacharset="UTF-8"><title>アンケート</title></head><body><h1>どのスポーツがやりたいですか?</h1><formmethod="post"><span>名前:</span><inputtype="text"name="name"><span>年齢:</span><inputtype="text"name="age"><span>血液型:</span><selectname="blood"><optionvalue="A">A型</option><optionvalue="B">B型</option><optionvalue="O">C型</option><optionvalue="AB">D型</option></select><inputtype="radio"name="favoriteSport"><span></span><inputtype="radio"name="favoriteSport"><span></span><buttontype="submit">投稿</button></form></body></html>

次にPugファイルを自動的にhtmlに変換してくれて、URLによって内容を変えることができるようにNode.js(JavaScript)ファイルを作っていきます。
以下のようなファイルを作ってください。

index.js
'use strict';consthttp=require('http');constpug=require('pug');constserver=http.createServer((req,res)=>{console.info('['+newDate()+'] Requested by '+req.connection.remoteAddress);res.writeHead(200,{'Content-Type':'text/html; charset=utf-8'});switch(req.method){case'GET':if(req.url==='/enquetes/base-soccer'){res.write(pug.renderFile('./form.pug',{path:req.url,firstItem:'野球',secondItem:'サッカー'}));}elseif(req.url==='/enquetes/tennis-basket'){res.write(pug.renderFile('./form.pug',{path:req.url,firstItem:'テニス',secondItem:'バスケ'}));}res.end();break;case'POST':letdata='';req.on('data',(chunk)=>{data=data+chunk;}).on('end',()=>{constdecodedData=decodeURIComponent(data);console.info('['+newDate()+'] 投稿: '+decodedData);res.write('<!DOCTYPE html><html lang="ja"><body><h1>'+decodedData+'が投稿されました</h1></body></html>');res.end();});break;default:break;}}).on('error',(e)=>{console.error('['+newDate()+'] Server Error',e);}).on('clientError',(e)=>{console.error('['+newDate()+'] Client Error',e);});constport=8000;server.listen(port,()=>{console.info('['+newDate()+'] Listening on '+port);});
解説
consthttp=require('http');constpug=require('pug');constserver=http.createServer((req,res)=>{console.info('['+newDate()+'] Requested by '+req.connection.remoteAddress);res.writeHead(200,{'Content-Type':'text/html; charset=utf-8'});

1行目でhttpモジュールをインストールしています。
2行目でpugモジュールをインストールしています。
3行目でhttpモジュールを使い、サーバーを作っています。
(req, res) => { は、サーバーが返すリクエストとレスポンスのオブジェクトです。
6行目のres.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
});
は、
レスポンスとしてヘッダに「ステータスコードが200(リクエスト成功)である」こと、「ファイルがhtmlである」こと、「文字コードがutf-8である」ことを書き込んでいます。

switch(req.method){case'GET':if(req.url==='/enquetes/base-soccer'){res.write(pug.renderFile('./form.pug',{path:req.url,firstItem:'野球',secondItem:'サッカー'}));}elseif(req.url==='/enquetes/tennis-basket'){res.write(pug.renderFile('./form.pug',{path:req.url,firstItem:'テニス',secondItem:'バスケ'}));}res.end();break;

1行目はswitch文の最初です。
2行目にはリクエストがGETである場合、
3行目にはurlが/enquetes/base-soccerである場合、
4行目のres.write()は()内の処理を直接ブラウザに表示します。
pug.renderFile('./form.pug'...は
上で説明した pug form.pugと同じ意味です。
5-7行目はpath: req.urlがurlが/enquetes/base-soccerであることを意味しており、urlが/enquetes/base-soccerである場合、firstItem: '野球', secondItem: 'サッカー'が
form.pugの以下の部分に当てはまるようになるということです。

input(type="radio" name="favoriteSport" value=firstItem)
      span #{firstItem} <= '野球が代入される'
      input(type="radio" name="favoriteSport" value=secondItem)
      span #{secondItem} <= 'サッカーが代入される'

GETメソッドでURLが/enquetes/base-soccer の場合、以下の画像のような表示になるかと思います。
スクリーンショット 2019-11-08 21.05.43.png

続きの部分の説明になります。

case'POST':letdata='';req.on('data',(chunk)=>{data=data+chunk;}).on('end',()=>{constdecodedData=decodeURIComponent(data);console.info('['+newDate()+'] 投稿: '+decodedData);res.write('<!DOCTYPE html><html lang="ja"><body><h1>'+decodedData+'が投稿されました</h1></body></html>');res.end();});break;default:break;

1行目はリクエストメソッドがPOSTである場合、
2行目は4行目で使用する変数dataを作成しています。
3-4行目は'data'イベントがあった場合の処理を表しており、
POSTで送られたデータがchunkにバイト文字として少しずつ代入され、data変数に代入されます。chunkはコンソールで表示すると以下のような文字になります。
スクリーンショット 2019-11-08 21.00.40.png
5行目からは'end'イベントがあった場合の処理が書かれています。
まず変数dataに代入されたバイト文字列はdecodeURIComponent(data);で、
デコードされて、バイト文字列が解除され、decodedData変数に代入されます。
その後、8行目で、res.writeでそのdecodedDataが表示されます。
10行目のres.end()で'end'イベントが終わります。
11行目以降のbreakやdefaultはswitch文の分岐処理です。

POSTメソッドの場合、ブラウザは以下のような表示になります。
POSTメソッドは、GETメソッドで取得したアンケートで、投稿ボタンを押すことを意味します。
スクリーンショット 2019-11-08 21.06.55.png

}).on('error',(e)=>{console.error('['+newDate()+'] Server Error',e);}).on('clientError',(e)=>{console.error('['+newDate()+'] Client Error',e);});constport=8000;server.listen(port,()=>{console.info('['+newDate()+'] Listening on '+port);});

1行目から5行目はエラー処理です。
6行目から9行目はサーバーが待ち受ける設定になります。
ポート番号8000番で待ち受けることを意味します。

解説が終わったので、
ファイルを作ったディレクトリ上で、以下のコマンドを打ちサーバーを立ち上げ、ちゃんと表示されるか確認しましょう。

$ node index.js
> [Fri Nov 08 2019 20:26:25 GMT+0900 (GMT+09:00)] Listening on 8000

以上のような表示が出たら、ブラウザで
http://localhost:8000/enquetes/base-soccer

http://localhost:8000/enquetes/tennis-basket
にアクセスしてみましょう。
・アンケートが表示されたこと
・URLによってアンケートの表示が違うこと
が確認できたら成功です。

参考

「N予備校 プログラミングコース」
https://www.nnn.ed.nico/
「HTMLタグリファレンス」
http://www.htmq.com/html/form.shtml
「PugでHTMLコーディングを効率化・メリットと使い方を知る」
https://tech.qookie.jp/posts/info-pug-feature/
「【Pug】ゴリラでもわかるJade改めPug入門」
https://blog.mismithportfolio.com/web/20160326pugbegin#d


http-authを使った簡単なBasic認証をやってみる

$
0
0

Basic認証とは

HTTPヘッダのAuthorizationに、エンコードされたIDとパスワードを含めて通信をすることで認証する方式。
暗号化されないBase64という方式でエンコードを行うため、HTTPで使用すると盗聴や改竄をされる恐れがある。
そのため使うときはHTTPSを使うべきであり、本格的な会員サイトなどには使うべきでない。

Digest認証

Basic認証のパワーアップ版
ユーザ名とパスワードをMD5でハッシュ化(暗号化)して送る。

Node.jsでBasic認証をやる方法

http-authライブラリ

//モジュールを参照varauth=require('http-auth');//basic認証の設定varbasic=auth.basic({//認証領域の設定(アクセスしているコンピュータやシステムの簡単な説明)realm:"Access to the staging site"},(username,password,callback)=>{//ユーザー名とパスワードの設定callback(username==="Tina"&&password==="Bullock");});// HTTPサーバーを作っている。http.createServer(basic,(req,res)=>{res.end(`Welcome to private area !`);/*
*
*
* サーバーへのリクエストやサーバーからのレスポンスの設定はここに書く
*
*/}).listen(8000);

この認証を使い、chromeデベロッパーツールのNetwork欄をみると、
ヘッダーをみることができ、Authorizationという部分にエンコードされた
ユーザー名とパスワードがある。
スクリーンショット 2019-11-08 23.24.22.png

Authorization: Basic Z3Vlc3QxOm5hN2hFcEV3
以上の部分のZ3Vlc3QxOm5hN2hFcEV3をコピーし、
コンソールでユーザ名とパスワードの確認ができる。

$ atob(Z3Vlc3QxOm5hN2hFcEV3)
> "ユーザー名:パスワード"

cradle で CouchDB にDBを作成しても save ができなかった(ように見えた)件

$
0
0

だめな例

varconn=new(cradle.Connection)(constants.DB_URL,constants.DB_PORT);vardb_master=conn.database('ddntj');db_master.create();db_master.save('hoge',{data:huga},callback(){...});

……とベタ書きするとこいつは saveしてくれません
原因は create() でDB作るのに(CouchDB側で)少しだけ時間がかかるのにその前にsaveが走るからです。
まぁ、当たり前っちゃ当たり前なのですがこれエラーも何も吐かないのでちょっと原因を探るのに時間かかりました。
create()でcallback関数が設定できればいいんですがどうやらそんな引数は無い模様。

動いた例

……で、どうしたかっていうと

varconn=new(cradle.Connection)(constants.DB_URL,constants.DB_PORT);vardb_master=conn.database('ddntj');db_master.create();setTimeout(()=>db_master.save('hoge',{data:huga},callback(){...}),1000);

単純ですが……これはアカンやつやろな……一応issueは送っておきました。実装してくれればいいんだけど。

kafka-proxy-ws を経由して Node.js へデータを渡す

$
0
0

こんにちは。
kafka-proxy-wsを見つけたので、react-websocket(クライアント App.js)へデータを渡してみました。ただし、"1\n2\n3"を渡して結果表示を見ると、最後の 3だけが処理されたように見えました(加算が 3だけです)。

Count: 1 → 4 → 7

$mkdir kafkaProxy
$cd kafkaProxy
$ vi server.js
$ vi package.json
$ npm install$ node server.js &
$echo-e"1\n2\n3" | kafkacat -P-b localhost -ttest$echo-e"1\n2\n3" | kafkacat -P-b localhost -ttest
$ npx create-react-app react-websocket
$cd react-websocket
$ vi src/App.js
$ npm start
App.js
importReact,{useState}from'react';importWebsocketfrom'react-websocket';functionApp(){const[count,setCount]=useState(1);functionhandleData(data){JSON.parse(data).forEach(res=>{setCount(count+Number(res.message));});}return(<div>Count:<strong>{count}</strong>
<Websocketurl='ws://localhost:9999/?topic=test&consumerGroup=group1'onMessage={handleData.bind(this)}/>
</div>
);}exportdefaultApp;
server.js
'use strict';constKafkaProxy=require('kafka-proxy');letkafkaProxy=newKafkaProxy({wsPort:9999,kafka:'localhost:9092/',});kafkaProxy.listen();
package.json
{"name":"kafkaProxy","version":"1.0.0","scripts":{},"description":"WebSockets based proxy for Kafka","author":"lawrips <lawrips@microsoft.com>","dependencies":{"debug":"^2.2.0","kafka-proxy":"^1.0.0","no-kafka":"^2.5.5","path-to-regexp":"^1.2.1","ws":"^1.0.1"},"main":"server.js","devDependencies":{"commander":"^2.9.0","should":"^9.0.2"},"repository":{"type":"git","url":"https://github.com/Microsoft/kafka-proxy-ws.git"},"keywords":["kafka","proxy","reverse proxy","ws","websockets","websocket","socket","sockets"],"license":"MIT"}

node.jsからAmazonS3へファイルアップロード

$
0
0

確認した環境

node.js: 10.17.0
s3-client: 4.4.2

s3-clientのインストール

s3-clientをインストールします。

npm i s3-client

s3クライアントのインスタンスを作成

imports3from"s3-client";constclient=s3.createClient({s3Options:{accessKeyId:ここにS3のアクセスキーID,secretAccessKey:ここにs3のシークレットキー,}})

s3へアップロード

s3-clientのreadmeによるアップロードのサンプルは以下です。

varparams={localFile:"some/local/file",s3Params:{Bucket:"s3 bucket name",Key:"some/remote/file",// other options supported by putObject, except Body and ContentLength.// See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property},};varuploader=client.uploadFile(params);uploader.on('error',function(err){console.error("unable to upload:",err.stack);});uploader.on('progress',function(){console.log("progress",uploader.progressMd5Amount,uploader.progressAmount,uploader.progressTotal);});uploader.on('end',function(){console.log("done uploading");});

サンプルのままだと使いづらいんで、Promiseオブジェクトでラップし、async/awaitが使えるようにします。

constuploadFileToS3=(bucketName,remoteDir,localFilePath)=>{returnnewPromise(async(resolve,reject)=>{constfileName=localFilePath.split('/').pop()constparams={localFile:localFilePath,s3Params:{Bucket:bucketName,Key:remoteDir+'/'+fileName// S3はオブジェクトストレージなのでフォルダの概念はないが、オブジェクトキーにスラッシュ区切りのパス名をつけることで、それをフォルダと解釈する},};constuploader=awaitclient.uploadFile(params);uploader.on('error',(err)=>{reject(err.stack)});uploader.on('end',function(){resolve()});});}

これで、async/awaitを使えるようになりました。

(async()=>{awaituploadFileToS3(s3backet,s3のフォルダ名,ローカルのファイルパス)})();

フォルダをまるごとアップロードしたい場合は、以下を使用します。

constuploadS3Dir=async(bucketName,localDir,remoteDir=null)=>{returnnewPromise(async(resolve,reject)=>{varparams={localDir,deleteRemoved:false,// default false, whether to remove s3 objects// that have no corresponding local file.s3Params:{Bucket:bucketName,Prefix:remoteDir,// See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property},};varuploader=client.uploadDir(params);uploader.on('error',err=>{reject(err.stack)});uploader.on('end',()=>{resolve()});});}

s3のファイルリスト取得

S3バケット名を指定し、そこにある全てのファイルリストを再帰的に取得します。

constlistS3Objects=async(bucketName,remoteDir=null)=>{returnnewPromise(async(resolve,reject)=>{varparams={recursive:true,// これをfalseにすると1階層のみの取得となるs3Params:{Bucket:bucketName,Prefix:remoteDir,// See: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#listObjects-property},};constfinder=client.listObjects(params);letobjects=[]finder.on('data',function(data){objects=data});finder.on('error',err=>{reject(err.stack)});finder.on('end',()=>{resolve(objects)});});}

s3にあるファイルの削除

S3バケット名と、オブジェクトキー文字列の配列を指定し、ファイルを削除します。

// objectKeys: [ remoteFile1, remoteFile2, ...]constdeleteS3Objects=async(bucketName,objectKeys)=>{returnnewPromise(async(resolve,reject)=>{varparams={Bucket:bucketName,Delete:{Objects:objectKeys.map(k=>({Key:k}))}};constdeleter=client.deleteObjects(params);deleter.on('error',err=>{reject(err.stack)});deleter.on('end',()=>{resolve()});});}

サンプル

S3のバケットをいったん空にしてファイルをアップロードするサンプルです。

// ここに上記の各関数を記述(async()=>{constconfigs={acckey:S3のアクセスキーID,seckey:S3のシークレットキー,bucket:S3のバケット名,}constclient=s3.createClient({s3Options:{accessKeyId:configs.acckey,secretAccessKey:configs.seckey,}})// 既存のobjectを削除constlist=awaitlistS3Objects(configs.bucket)constkeys=list.Contents.map(i=>i.Key)//console.log(keys)if(keys.length>0)awaitdeleteS3Objects(configs.bucket,keys)awaituploadFileToS3(configs.bucket,'foo','./bar.txt')// このプログラムの実行フォルダ直下に、`bar.txt`ファイルが存在する前提})();

visual studio code で、babel-node(@babel/node)をデバッグ

$
0
0

確認した環境

visual studio code: 1.39.2
babel-node: 7.2.2

launch.jsonを開く

画面右端からデバッグアイコンをクリックし、デバッグの設定アイコンをクリック、プルダウンリストから「Node.js」を選択すると、デフォルトのlaunch.jsonが表示されます。
sas.gif
デフォルトのlaunch.json

{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "プログラムの起動",
            "program": "${file}"
        }
    ]
}

launch.jsonを編集

launch.jsonに以下のように編集します。※メインのjsがindex.jsである前提です。異なる場合は、"program"の値を書き換えてください。

{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "babel-nodeのデバッグ",
            "program": "${workspaceFolder}/index.js",
            "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/babel-node",
        }
    ]
}

デバッグの実行

画面右端からデバッグアイコンをクリックし、上で編集した設定名("name")が選択されているのを確認し、デバッグの開始アイコンをクリックします。
qq.gif

Viewing all 8821 articles
Browse latest View live