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

ReactのBDD勉強としてサービス作ってみた

$
0
0

TL;DR

  • フロントエンドBDDの勉強用に、簡単なサービス作った。
  • テスト書くの良い。
  • サービス公開とか色々本筋でないとこに結構引っかかった。
  • Javascript難しい。
  • フロントエンド難しい。

What's this?

最近業務でswiftを触っていたのもあり、勉強の方向性がフロントエンドに偏りつつある。
Reactは以前から学習に取り組み、簡易な実装経験があったが、
そもそも機能を実現することも重要だが、システムとして長期運用していく上で重要になるテストコードについても勉強したいと思ったので、じゃあReactでテスト書きつつシステム一個作って見よう!という試みで開始した。

作ったもの

image.png

サービス:https://timer-d73c3.web.app
GitHub:https://github.com/theMistletoe/RecoSta

起動中の経過時間を計測してるサービス。勉強時間・作業時間を記録することが目的。
エンジニアたるもの20h/weekは勉強するのは最低ラインとのことで、
私はどのくらいできているかな〜というのが気になった、ところから作ってみました。

syougakusei.png

使用したテストライブラリ

https://github.com/testing-library/react-testing-library

react-testing-libraryというライブラリで、Reactに置ける振る舞い駆動テストを実現するためのライブラリを使った。
swiftでもBDDをしていたこともあり、今後の潮流的にもBDD来てそうだなと思ったので使ってみた。
参考で詳しく書いてくださってる記事を載せています。

実際に書いてたテストコードが下記

describe("Main Page",()=>{it("get and display studytimes",async()=>{constspy=jest.spyOn(axios,'get').mockImplementation(()=>{return{data:[{date:'20191121',studytime:'2315'},{date:'20320408',studytime:'444'}]}});const{getByText,getAllByTestId,getByPlaceholderText}=awaitrender(<Main/>);awaitwaitForElement(()=>getAllByTestId("studytime-list"));expect(firebase.auth().currentUser.getIdToken).toHaveBeenCalled();expect(spy).toHaveBeenCalledWith(`${process.env.REACT_APP_BACKEND_ENDPOINT}/api/v1/studytime`,{headers:{authorization:`Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`}});expect(getByText("Total: You've studieds 2759 seconds!")).toBeInTheDocument();expect(getByPlaceholderText("Input Your Email Address")).toBeInTheDocument();expect(getByText("Now, You've studied")).toBeInTheDocument();expect(getByText("Date")).toBeInTheDocument();expect(getByText("Studied Times(s)")).toBeInTheDocument();expect(getByText("20191121")).toBeInTheDocument();expect(getByText("2315")).toBeInTheDocument();expect(getByText("20320408")).toBeInTheDocument();expect(getByText("444")).toBeInTheDocument();});}

BDDっぽいところでいうと、
expect(getByText("20191121")).toBeInTheDocument();
みたいにHTMLの画面上に想定通りの文字や入力欄が存在するかどうかをテストコードで表現できる。
こうすることで、ユーザーからみた挙動に近い形で、テストコードをSpecとして表現できる。

システム構成

構成自体はJAMStack?のはず?です。

システム構成図 (1).png

Firebaseを中心に活用しつつ、バックエンドだけHeroku使ってる感じです。
Firebase HostingにReactのフロントエンドをデプロイしています。
バックエンドもNode.js(express)でさっくり書いています。

データストアはFireStoreというFirebaseのNoSQLを使用しています。エンティティ設計がちとめんどくさいですが、簡単なサービスを作るときには簡単にできて良い感じですね。

認証にもFirebase Authenticationを使用していて、メールアドレスによる認証を行なっています。
この辺の認証周りとかすごいめんどくさかった、、自分が理解できていないこともあるが、、、

そもそもFirebaseのmBaasの考え方的にバックエンドを挟む構成がいまいち向いていない感じがありますね。初めはバックエンド書かずにやろうとしたんですが、axiosのHTTPリクエストのテストがしたかったのと、よくわからなかったので逃げてしまった、、、

まとめ

テスト・BDDの勉強のためにサービス作りながら実際に適用できそうか勉強しながらやってみて、
BDD・TDDを実践しながら作ると、個人開発であっても実装に迷うことが少なくなり、方向性を見失うことはなかったように思う。
が、ここで早くなったと書かなかったのは、テストの書き方とか、Jestわからんとか、Mock効かねぇとか、テスト周りで実装に詰まることが格段に増え、すっごい時間がかかってしまった。

そもそもJavascriptへの理解が全然追いついていないとひしひしと感じた。バックエンドで使ってるような言語のスタンスで取り組むと、全然期待する挙動にならずオワタ...になる。

一旦ベースができたので、 実はテスト書けてないところとか、設計オワコンなところとか、改善点を直しながら理解を深める活動ができればいいな、と思っている。

参考


Electronで「要素の検証」を再現する方法

$
0
0

本文

初投稿です。
Chromeで右クリックすると出てくる「検証(I)」。
押すとデベロッパーツール内でカーソル直下の要素にジャンプします。
Electron内でも使えると開発中なにかと便利なので、この機能を再現してみます。

openDevTools

Electronで単にデベロッパーツールを開く場合はwebContents.openDevTools([options])を使います。

const{remote}=require('electron')constwebContents=remote.getCurrentWindow()webContents.openDevTools()

modeオプションで表示位置の指定ができます。

mode状態
right画面右に表示
bottom画面下に表示
detach別ウィンドウに切り離して表示
undocked別ウィンドウに切り離すが画面内に戻すこともできる
webContents.openDevTools({mode:'undocked'})

※webviewタグによって作られたwebContentsにはmodeの指定ができないようです。(detachのみ)
https://electronjs.org/docs/api/web-contents#contentsopendevtoolsoptions

inspectElement

一方、webContents.inspectElement(x, y)は指定された位置の要素をデベロッパーツール内で開きます。

以下はマウス右クリックで「要素の検証」メニューを表示するサンプルです。
右クリックを使うアプリだと、機能が干渉してしまうので僕は中央ボタンに割り当てています。

const{remote}=require('electron')const{Menu,MenuItem}=remoteconstwebContents=remote.getCurrentWindow()// 2 にするとマウス中央ボタンでメニュー表示consttriggerButton=3letclickPosition=nullconstcontextMenu=newMenu()constinspectElement=newMenuItem({label:'要素の検証',click(){webContents.inspectElement(...clickPosition)}})contextMenu.append(inspectElement)constonclick=({x,y,which})=>{if(which===triggerButton){event.preventDefault()clickPosition=[x,y]contextMenu.popup(webContents)}}window.addEventListener('auxclick',onclick)

webpackを使っている場合は以下のif文で囲み、プロダクションビルドで除外することをおすすめします。

if(process.env.NODE_ENV!=='production'){...}

https://electronjs.org/docs/api/web-contents#contentsinspectelementx-y

参考

https://electronjs.org/docs/api/web-contents
https://stackoverflow.com/questions/32636750

年末の断捨離アプリをFirebaseでhostingしてみた話(webアプリが少しずつ良くなっていく過程編)

$
0
0

Firebaseのhostingを使ってReactのSPAアプリをデプロイした話

です。
結論だけ話すとそんな感じです。(domainをとっていないことをお許しください)
https://dan-syari.web.app/#/

年末の短期休暇でアプリを1つ作ろうと考えていて、今回作りました。(開発期間は2日間)

今回作ったアプリについて

  • タイトル: 「Dan Syari」
  • 内容: 来年したい目標を3つ立て、それについて簡単な質問に答えるとその目標の中で重要なことがわかる
  • 技術:
    • React
    • Typescript ☆
    • React Hooks ☆
    • Reduxを使わない状態管理 (Context) ☆
    • Firebase (Hosting) ☆
    • Firestore ☆
    • Material UI ☆
    • SPA ☆

☆がついているものは著者は初めて使ったものです

洗い出して列挙してみると、結構挑戦してたことがわかります

本記事の対象者

  • 意気揚々と壮大なアプリを作ろうとして途中で断念してしまう人への解決方法
  • 最初から設計ばかり気にして、手が動かない人
  • 年末で暇すぎて、ゲームしたり漫画読んでたり、とにかく暇人なあなた!!

本記事で伝えたいこと

  • ちっぽけなところから初めて、少しずつ大きくしていくほうが楽しい
  • どんなアプリもデプロイしないと始まらない
  • 作っていくうちに設計のミスに気付いて後悔するけど悪いことばかりではない
    • 個人の失敗は、個人の開発や仕事での開発に活かせばいい!!

本記事の流れ

  • 開発の初期段階のラフ画から、デプロイして少しずつ現在(2019/12/30)の状態までを画像を比較することで追っていきます
  • 以下のような順番でいこうと思います
    • コーディング前のラフ画
    • 最初のデプロイ
    • 1日目の途中経過
    • 1日目の終わり
    • 2日目の途中経過
    • 2日目の終わり
    • 3日目のオプション
  • 年末なのに疲れたくないと思うので、軽くスワイプしてざっーと見るぐらいでいいので読んでみてください
    • 中堅の方達には初期の開発の時の気持ちを思い出していただけると思います
    • 開発初心者に近い人たちにとってはこうやって進めていけば続くのか!という1つの指標みたいな感じにしてもいいかもしれないです

コーディング前のラフ画

  • いきなりコーディングするのは避け、イメージだけでもいいので紙に書き起こしましょう
  • 仕事で開発する場合は、 FigmaSketchなどのデザインツールを使うのもいいでしょう
    • 個人的にはFigmaを使っていますが、個人開発のハッカソンレベルのものであれば紙で十分なので、今回は紙です

トップページ
トップページ

質問ページ
質問ページ

結果画面
結果画面

最初のデプロイ

  • デプロイはFirebaseのHostingサービスを使いました
  • domainを取得しなければ基本的に無料で、コマンド一発でデプロイでき、FirestoreというDBも使うことができる最強のサービスです
    • ぜひ使いましょう(僕が学生の時に知っていたら本当によかった)
  • 最初のデプロイはトップページだけでした
    • 全く使い物にならないけど、デプロイって楽しいから何度でもしたくなります
    • そのうち、デプロイしたくて開発している自分に気付きます(☆ここ重要!!)

top

1日目の途中経過

  • 画像を見ればわかりますがほとんど進んでいません
  • Typescriptとか、React-routerとかほとんど経験がなかったので悪戦苦闘しました
  • ここら辺で、自分が作っているものは意味があるのかという自問自答タイムがありました
    • これを打破できたのは細かいcommitと、とりあえずデプロイするみたいなことをやっていたら手が動き始めたからです

top

  • 無限に質問が作れてしまうところからのスタートでした

question

1日目の終わり

  • 結果画面以外の簡単な実装が終了しました
  • ここからは修正があった部分だけ画像をつけます(枚数が増えてしまうので)

  • トップページのフォームの「断捨離る」ボタンを全てのフォームに記入していないと押せないようにしました(validation)

top form

  • 3つの目標に対してそれぞれ質問ページを作りました
    • ラフ画の時点では「はい・いいえ」でしたが、最終的に「0 ~ 10」で選択できるようにしました
      • グラフ作りやすいし、高い低いを Yes or No の2値で選ぶのなんか違うなと思ったからです

question

  • 悲しいことに1日目の終わりの時点では結果画面まで行かなかったです
  • この時点で開発開始から12時間経ち、ベッドへダイブしました
  • これぐらいは1時間で終わらせられるだろ!!というお気持ちの人もいると思いますが、今回の技術はほとんど初挑戦だったのでお許しください
    • 初めての技術はほとんど全部つまづくので温かい目でみていただけるといいですね
    • どんなエンジニアだって最初はこんなもんです。これから開発したい!と思っている初学者のかたも諦めずに頑張ってください!

result

2日目の途中経過

  • 各種質問に対する答えとして「0 ~ 10」をhtml標準の <input type="range">を使って実装しました
  • 結果画面にグラフで3つの目標の結果画面を表示できるようにしました

question

  • 結果画面にrechartsを使って、
    • 2日目途中でこれだけ?と思われるかもしれませんが、Typescriptを初挑戦していて、型付けの部分でとんでもなく苦労しました
    • rechartsというグラフ描画用のpackageを、別のプロジェクトにjsで作成し、そこなら簡単に呼び出せるのに、なぜかTypescript(ts)で呼び出そうとすると映らず、一生描画できないのではないかと絶望しました
      • 結果的にTypescriptでは型付けのマッピングみたいなことをしたファイルがないと、型がわからず呼び出すことができていなかったです(@types/~ のpackageを追加しました)
    • 得意じゃない、英語の記事を漁ってでも意地でも答えを見つけに向かいました(公式ドキュメントとかは最初に読みましょう)

result

2日目の終わり

  • 2日目の途中の段階でやりたいことはほぼ終わったので、あとは微調整をしていくことができる状態になったので進捗が少し捗った
  • トップのフォーム部分の文字の色が気に食わなかったので修正した
    • 現状が良いのかという議論はあるけど、最初がダサすぎた

top form

  • Material UI を導入し、 Input とか Button あたりの既存のものを置き換えた
    • これだけで雰囲気出てくる
    • CSSフレームワーク大事(というかデザイナーさんは神)

question

  • 結果画面にグラフだけじゃなくて、それぞれの項目の可視化もしてみた
  • 星の大きさは「Dan Syari」的にいえば、あなたにとって大切なことを大きさで表している
    • 大きい星ほど、あなたにとって大切なことである
    • グラフを象限としてみたときに、第一、第二、第四、第三の順に大切なものになる
    • 重要なことがあなたにとって大切なことですよ、ということを伝えたいのだ

result 1

  • はっきり言って、こんなに文字があるとうざいなと、スクショしてみて強烈に感じた
    • 次の開発には生かしたい

result 2

象限

まとめ

  • さぁ、いかがだったでしょうか
  • 少しずつアプリが良くなっていくのが見えたのではないでしょうか
  • ぶっちゃけ、現時点でめっちゃおしゃれで、実用性があって、いろんな人に響くかときかれれば耳が痛くなるでしょう
    • でも、声を大にして言いたい、「最初はこんなもんだよ!」
    • 家族や、友達、知り合いに使ってもらってそれだけで作ってよかったって思えるのも本当に大事だと思います
    • エンジニアとして同じ技術を使い慣れて実装力を上げていくことも大事です
    • が、コンフォートゾーンから抜け出すこともときには大切です
    • 小さくてもダサくても良いから1年のうちに新しい技術に手を出してみること大事
  • だということが「Dan Syari」を使っていただけるとわかります
    • ぜひ試してみてください!!

https://dan-syari.web.app/#/

3日目のオプション

  • OGP設定
    • TwitterとかFacebookとかに貼ったときにでるサマリみたいなあれです
    • ぜひTwitterやFacebookに貼って、友達や知り合いに送ってみてください
  • ちょっとしたデザイン微修正(margin/paddingとか入力欄のwidthとか)
  • Firestoreの導入

top form

slack

  • めちゃくちゃダサいw
    • でも個人的には結構好き

使った技術の解説などは別の記事として作ろうと思います
少しでも面白いなと思った人や、年末の暇つぶしができて嬉しいなという人や、解説記事も読んでみたいなという人は「いいね!」してくださると嬉しいです(この記事にリンク追加したときに通知が飛ぶようになるので是非、「いいね!」しておくといいかもしれません)

VuePress でテンプレートから記事生成

$
0
0

最近 VuePress で個人ブログの運用をはじめました

ですが、VuePress 単体では記事の Markdown を自動で生成できないことに悩んでいたので、{{ mustache }}を使った簡易的な記事生成スクリプトを作りました。

イメージ的には hexonew コマンドのような感じです。

登場人物

new-post.jstemplates/*.mustacheを新規に追加します。

$ tree vuepress
.
├── new-post.js <------------------------- スクリプト本体
├── package.json
├── docs <-------------------------------- VuePress のプロジェクトフォルダ
│   ├── .vuepress
│   │   └── config.yml 
│   └── _posts
│       └── 2019-12-31-newpost.md <------- 生成される markdown
└── templates <--------------------------- テンプレート フォルタ
    ├── base.mustache
    └── header.mustache

スクリプトの実装

まずは、スクリプトです。mustacheを使うのでパッケージを入れておきます。

$ yarn add --dev mustache

スクリプト本体は git で管理してますがここにも記載しておきます。そんな大したことはしてません。

機能:

  • 引数で次の 3 つを指定
    • ベースとなるテンプレート
    • 記事のタイトル
    • (optional) markdown のファイル名
  • ベースとなるテンプレートに「タイトル」と「日付」を埋め込んで出力
  • 出力するファイルは 2019-12-13-lower-case-title.mdのような名前
new-post.js
'use strict';constfs=require("fs");constpath=require('path');constmustache=require('mustache');functionformatedateDate(date,includesDatetime){constdateStr=[date.getFullYear(),("0"+(date.getMonth()+1)).slice(-2),("0"+date.getDate()).slice(-2)].join("-");constdatetimeStr=[("0"+date.getHours()).slice(-2),("0"+date.getMinutes()).slice(-2),("0"+date.getSeconds()).slice(-2),].join(":");returnincludesDatetime?dateStr+""+datetimeStr:dateStr;}functionsanitizeTitle(title){returntitle.replace(/[ ?!@;'\\.]/g,'-').toLowerCase().replace(/^[ -]+|[ -]+$/g,"");}/** Parameters */constpostDir=path.join("docs","_posts");consttemplateEngine={name:"Mustache",dir:"templates",extension:".mustache",};/** Parse arguments */constusage="Usage: node new-post.js <template-name> <title> [<title-for-filename>]";constargs=process.argv.slice(2,process.argv.length);if(args.length!=2&&args.length!=3){console.error("Invalid arguments")console.error(usage);process.exit(1);}consttemplatePath=path.join(templateEngine.dir,args[0]+templateEngine.extension);consttitle=args[1];constfiletitle=args[2]?sanitizeTitle(args[2]):sanitizeTitle(title);constpostFilename=formatedateDate(newDate(),false)+"-"+filetitle+".md";constpostPath=path.join(postDir,postFilename);/** Main */fs.readFile(templatePath,'utf8',function(err,templateData){if(err)throwerr;// render the template with mustache.constrenderedData=mustache.render(templateData,{title:title,date:formatedateDate(newDate(),true),overview:title+"についての記事です。",});// write a markdown file out.fs.writeFile(postPath,renderedData,{flag:'wx'},function(err){if(err){throwerr;}console.log("Created a new post at: "+postPath)});});

テンプレートの実装

templates/<template-name>.mustacheにテンプレートを置いていきます。

base.mustache
---
title: "{{title}}"
date: {{date}}
category: その他
tags:
  - タグ
---

{{overview}}<!-- more -->

# {{title}}

[[toc]]

## はじめに

new-post.js内の以下の部分が、テンプレートに値を代入して markdown をレンダリングする処理に相当します。

// render the template with mustache.constrenderedData=mustache.render(templateData,{title:title,date:formatedateDate(newDate(),true),overview:title+"についての記事です。",});

使い方

$ node new-post.js <template-name> <title> [<title-for-filename>]

# e.g.$ node new-post.js base "はじめての Node.js" getting-started-with-nodejs

高解像度降水ナウキャストの画像をpuppeteerによってロボット的に取得

$
0
0

これは何ですか?

気象庁の降水レーダー画像を取得するスクレーパーです。今はこれもロボットと言えば言えるようです。長らくWatir + ChromeDriverで頑張ってきましたが、解析の困難なエラーが出るようになって、ChromeDriverがいくら更新しても直ってこないので、迂回策としてpuppeteerで書いてみたところあっさり動作した次第です。

高解像度降水ナウキャストの画像取得 by puppeteer/node.js

習作以上の物ではございませんが、お役に立てれば幸いです。

ウェブスクレイピング

$
0
0

概要

CSVに記入したURL一覧からページの情報をスクレイピングします。

ソース

package.json

{
  "name": "scraping",
  "version": "1.0.0",
  "description": "scraping",
  "author": "",
  "devDependencies": {
    "cheerio": "^1.0.0-rc.3",
    "csv-parser": "^2.3.2",
    "fs": "0.0.1-security",
    "json2csv": "^4.5.4",
    "request": "^2.88.0",
    "request-promise": "^4.2.5"
  }
}
  • cheerio ・・・ 読み込んだページをjQuery形式でDOM操作できる
  • csv-parser ・・・ CSV形式をJSONに変換
  • fs ・・・ ファイル操作
  • json2csv ・・・ JSON形式をCSVに変換
  • request ・・・ HTTP通信を行う
  • request-promise ・・・ HTTP通信を行う

url.csv

Yahoo!ニュースの記事情報をスクレイピングします。

url
https://news.yahoo.co.jp/topics/top-picks
https://news.yahoo.co.jp/topics/domestic
https://news.yahoo.co.jp/categories/world

scraping.js

constfs=require('fs');constrequest=require('request');constrp=require('request-promise');constcsv=require('csv-parser');constcheerio=require('cheerio');const{Parser}=require('json2csv');constresults=[];constlist=[];letcount=0;letfilename='dist/data.csv';letloadfile='url.csv';fs.createReadStream(loadfile).pipe(csv()).on('data',function(data){results.push(data);}).on('end',function(){crawl(results[count]);});functioncrawl(result){leturl=result.url;let_include_headers=function(body,response,resolveWithFullResponse){return{response:response,$:cheerio.load(body),body:body};};constoptions={method:'GET',uri:url,json:true,transform:_include_headers,};rp.get(url,options).then((data)=>{scrp(data.$,url,data.response,data.body);}).catch((error)=>{console.log(error);});}functionscrp($,url,response,body){letcategory=$('.newsFeedTab_item-current').text();$('.newsFeed_item').each(function(){lettitle=$(this).find('.newsFeed_item_title').text();letdate=$(this).find('.newsFeed_item_date').text();lethref=$(this).find('.newsFeed_item_link').attr('href');letthumbnail=$(this).find('.thumbnail img').attr('src');if(href!==undefined){letobj={category:category,title:title,date:date,href:href,thumbnail:thumbnail,}list.push(obj);}});count++;console.log('Complete:'+url);if(count<results.length){crawl(results[count]);}else{write();}}functionwrite(){letfields=['category','title','date','href','thumbnail'];constopts={fields};try{constparser=newParser(opts);constcsv=parser.parse(list);fs.writeFile(filename,csv,function(err){if(err)throwerr;});}catch(err){console.error(err);}}

Github

これから NestJS をはじめようとしている方へ

$
0
0

この記事は NestJS アドベントカレンダー 2019の 25 日目の枠です。

はじめに

NestJS の知名度は 2019 年を通して上昇したように感じられますが、まだまだ日本語情報が少ないという思いからこのアドベントカレンダーを開始しました。

まず、 NestJS とは何か、どういうユースケースにマッチするか、という紹介については以下のスライドにまとまっています。

What is NestJS? / @potato4d
https://speakerdeck.com/potato4d/what-is-nestjs-number-nestjs-meetup

次に 1 日目 〜 7 日目では、 NestJS を触ってみてアプリケーションを作るまでの具体的な作法を、コードを交えて紹介しています。
8 日目以降は NestJS を使っていて当たりがちなつまづきポイントなど、1記事完結の tips をテーマとして構成しました。

以下では、 NestJS に対して思われがちと考えられる疑問について回答をします。私の主観も入っている箇所はありますが、 NestJS を選定する際の参考にしてみてください。

NestJS って Angular っぽくない? / Angular やってないと分からなくない?

NestJS が Angular の影響を受けている側面が大きいことは否定しません、 Angular 経験者の方が馴染み易いとは思います。
しかし、 Angular の経験者向け/経験者でないと難しい、ということは一切ありません。
特に Angular に似ている部分としては Module と DI 、 Decorator かと思いますが、このうち DI と Decorator に関しては TypeScript や Java 等のバックエンドフレームワークでの開発を行なった方であれば、経験している方も多いかと思います。
Module についても、 JavaScript の Module システムのスコープが広がりすぎてしまうものを厳密に定義し、 DI に役立てている程度のものだという理解をしておけば基本的には問題ありません。
Module と DI については NestJS の Module と DI を理解するを読んでいただければ、理解の助けになるのではないかと思います。

NestJS ってデカくない?

express 比では相当に大きく感じますが、上記のスライドにある通り、 express のみで効率的な開発を維持するには現実的には制約が多いです。
得に、 express には定められたアーキテクチャが存在しないため、アーキテクトとなる開発者がチームを離脱した際に路頭に迷うという話も聞きます。
上記で Angular の経験が無くても良いという断りをした上での引用ですが、 Angularでの開発を快適に進めるために知っておきたいこと100点か50点か、それより全員80点をと仰っているのは NestJS にも当てはまる考え方であり、フレームワークのもたらす恩恵というものが自分たちにとって有益であるかどうか、ということを軸に採用を判断するのが良いと思います。

また、バックエンドフレームワークとしては現代の多様化するフロントエンドやデータベースに適する形であり、 UI 層と永続化層を内包していないため、任せられる責務は実は限定的であります。

Swagger でのドキュメント生成って本当にいいの?

手でドキュメントをメンテナンスされるのが正しい情報が保たれるとは思いますが、メンテナンスされないドキュメントが残るよりは、自動生成で拙くてもコードに距離が近い場所でメンテナンスされるドキュメントがある方が健全なプロジェクトだとは思います。
Swagger については NestJS の @nestjs/swagger でコントローラーから Open API(Swagger) の定義書を生成すると、以下のスライドが参考になると思います。

NestJS アプリケーションから Swagger を自動生成する / @euxn23

https://speakerdeck.com/euxn23/nestjs-meetup-tokyo-01

NestJS って遅くない?

まず、バックエンドアプリケーションの遅さの性質としては大きく分けて2つあります。

  • アプリケーションの起動速度が遅い
  • レスポンス速度が遅い

express をベースとしたアプリケーションと比較した際に、アプリケーションの起動速度の遅さは目立つかもしれません。それゆえ、 firebase functions や lambda で使用するのには向いているとは言えませんが、そもそも FaaS を使用する場合は NestJS は選択肢に上がらないと思います。
レスポンス速度については、 express 比でわずかに遅いところはありますが、そもそも express がいわゆる LL としては十分に速すぎるので、実用上ほぼほぼ問題のない速度でレスポンスが返ります。
どちらかというと永続化層の方がネックになるかと思われますが、それでもアプリケーション層に不安
が残る場合は想定されるリクエスト数に対してのパフォーマンステストを行なって判断するのが良いと思います。

NestJS と ClassValidator / TypeORM って一緒に使わないといけないの?

TypeScript であり、 Decorator をベースとした記述が可能という点を除いて、特に関連性のあるものではありません。
必要に応じて採用したりしなかったりするのが良いと思います。

Decorator が不安

まず Decorator が内部で何を行なっているかについては TypeScript の Decorator と継承で説明しています。
Decorator の仕様自体が不安定な中で先行実装があることについて不安はあるかと思いますが、 TypeScript のビルドが後方互換を保つ限りは動作しなくなるということはないでしょう。
Parameter Decorator で副作用を起こして動作する Class Validator 等のライブラリへの不安はあるかと思いますが、 NestJS を通常の範囲で使う上では、 Parameter Decorator は (Swagger 等の Production に不要なものを除いて) 不要なので、ここは懸念する必要はありません。
また、 Decorator を用いるライブラリを組み合わせる場合に Decorator Hell になることへの懸念については、 TypeScript の Decorator Hell を解消するで 1 つの解決へのアプローチを書いているので、ご参考にしてください。

サーバサイド TypeScript に不安がある

フロントエンドでの Webpack を使用したビルドとは異なり不安になる箇所もあるかと思いますが、 NestJS ではプロダクションのビルドまでサポートしています。
また、 tsconfig-paths がバックエンドで使えない、という経験があるかと思いますが tsconfig の path alias 解決に tsconfig-paths/register を node で使う方法と TS 依存の分離方法で解決方法を紹介していますのでご参考ください。

まとめ

以上で NestJS の概要および疑問点のご説明をしました。他にも NestJS についても疑問点がある場合は、 NestJS Japan Users Group の discordでお気軽にお聞きください。
また、現在 日本語ドキュメントについても準備を進めている段階です
本家の公式ドキュメントの情報量が膨大で、基本的なことはそちらにも書いてあるため、日本語化することによってより多くの方に届けられればと思っています。

謝辞

この度 NestJS Japan Users Group としても活動を開始して、当初私を @potato4dの2名だけで書く予定だったこのアドベントカレンダーも、何件も書いて頂きました。ご協力頂いたみなさまありがとうございました。
今後も NestJS が必要な人に正しく届くよう、情報発信を継続して行なっていきたいと思いますので、是非ご協力頂ければと思います。

Mac で npm install すると Cannot find module './lib/async' が出るようになったので、Nodeとnpmを削除して、nodebew で node を再インストールした時のメモ

$
0
0

はじめに

タイトルのように、npm install すると [Cannot find module './lib/async'] と表示されてインストールが進まなくなって、色々調べて結果 Node.jsを削除して、再度インストールした時のメモです。

自分のメモ書きなので特にまとめもありませんし、役立つ情報も無いかもしれませんが残しておきます。

現象

npm -g でインストールしたのに、依存ライブラリが無いと言われる。
node.js のパスがおかしい感じ。

ログ

$ npm root -g
/Users/kazoo/.npm-global/lib/node_modules

$ node
Welcome to Node.js v12.12.0.
Type ".help" for more information.
> .help
.break    Sometimes you get stuck, this gets you out
.clear    Alias for .break
.editor   Enter editor mode
.exit     Exit the repl
.help     Print this help message
.load     Load JS from a file into the REPL session
.save     Save all evaluated commands in this REPL session to a file

Press ^C to abort current expression, ^D to exit the repl
> global.module.paths
[
  '/Users/kazoo/repl/node_modules',
  '/Users/kazoo/node_modules',
  '/Users/node_modules',
  '/node_modules',
  '/Users/kazoo/.npm-global/lib/node_modules',
  '/Users/kazoo/.node_modules',
  '/Users/kazoo/.node_libraries',
  '/usr/local/Cellar/node/12.12.0/lib/node'
]

仮説的な

nodeは homebrewでインストールしているが、もともとのnodeは別でインストールしている(多分。記憶に無い)それが色々悪さしていると思われる。

こんな感じ

$ ls -la
total 16
drwxr-xr-x   5 kazoo  staff  160 11 13 04:49 .
drwxr-xr-x   4 kazoo  staff  128 11 13 04:45 ..
drwxr-xr-x  10 kazoo  staff  320 11 13 04:49 .git
-rw-r--r--   1 kazoo  staff  140 11 13 04:48 .gitignore
-rw-r--r--   1 kazoo  staff  468 11 13 04:48 package.json
$ npm install
npm ERR! code MODULE_NOT_FOUND
npm ERR! Cannot find module './lib/async'
npm ERR! Require stack:
npm ERR! - /Users/kazoo/.npm-global/lib/node_modules/npm/node_modules/normalize-package-data/node_modules/resolve/index.js
npm ERR! - /Users/kazoo/.npm-global/lib/node_modules/npm/node_modules/normalize-package-data/lib/fixer.js
npm ERR! - /Users/kazoo/.npm-global/lib/node_modules/npm/node_modules/normalize-package-data/lib/normalize.js
npm ERR! - /Users/kazoo/.npm-global/lib/node_modules/npm/node_modules/read-package-json/read-json.js
npm ERR! - /Users/kazoo/.npm-global/lib/node_modules/npm/node_modules/read-package-tree/rpt.js
npm ERR! - /Users/kazoo/.npm-global/lib/node_modules/npm/lib/install.js
npm ERR! - /Users/kazoo/.npm-global/lib/node_modules/npm/lib/npm.js
npm ERR! - /Users/kazoo/.npm-global/lib/node_modules/npm/bin/npm-cli.js

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/kazoo/.npm/_logs/2019-11-13T02_33_59_519Z-debug.log
$ which node
/usr/local/bin/node

$ which npm
/Users/kazoo/.npm-global/bin/npm

$ which brew
/usr/local/bin/brew

$ brew config
HOMEBREW_VERSION: 2.1.16
ORIGIN: https://github.com/Homebrew/brew
HEAD: 3aa7624284c43180a3f3a71aeaa9263092868e12
Last commit: 8 days ago
Core tap ORIGIN: https://github.com/Homebrew/homebrew-core
Core tap HEAD: 21e4508164a772f3ebd7964ea3d59f195750ef52
Core tap last commit: 4 days ago
HOMEBREW_PREFIX: /usr/local
CPU: quad-core 64-bit haswell
Homebrew Ruby: 2.6.3 => /usr/local/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3/bin/ruby
Clang: 6.0 build 600
Git: 1.9.3 => /Applications/Xcode.app/Contents/Developer/usr/bin/git
Curl: 7.54.0 => /usr/bin/curl
Java: 1.8.0_101
macOS: 10.13.6-x86_64
CLT: 10.1.0.0.1.1539992718
Xcode: 6.1.1
$ brew doctor
Please note that these warnings are just used to help the Homebrew maintainers
with debugging if you file an issue. If everything you use Homebrew for is
working fine: please don't worry or file an issue; just ignore this. Thanks!

Warning: "config" scripts exist outside your system or Homebrew directories.
`./configure` scripts often look for *-config scripts to determine if
software packages are installed, and which additional flags to use when
compiling and linking.

Having additional scripts in your path can confuse software installed via
Homebrew if the config script overrides a system or Homebrew-provided
script of the same name. We found the following "config" scripts:
  /Users/kazoo/anaconda/bin/icu-config
  /Users/kazoo/anaconda/bin/freetype-config
  /Users/kazoo/anaconda/bin/xslt-config
  /Users/kazoo/anaconda/bin/libpng16-config
  /Users/kazoo/anaconda/bin/python3.6m-config
  /Users/kazoo/anaconda/bin/libpng-config
  /Users/kazoo/anaconda/bin/xml2-config
  /Users/kazoo/anaconda/bin/python3-config
  /Users/kazoo/anaconda/bin/curl-config
  /Users/kazoo/anaconda/bin/python3.6-config

Warning: Unbrewed header files were found in /usr/local/include.
If you didn't put them there on purpose they could cause problems when
building Homebrew formulae, and may need to be deleted.

Unexpected header files:
  /usr/local/include/node/android-ifaddrs.h
  /usr/local/include/node/ares.h
  /usr/local/include/node/ares_build.h
  /usr/local/include/node/ares_rules.h
  /usr/local/include/node/ares_version.h
  /usr/local/include/node/nameser.h
  /usr/local/include/node/openssl/archs/BSD-x86/opensslconf.h
  /usr/local/include/node/openssl/archs/BSD-x86_64/opensslconf.h
  /usr/local/include/node/openssl/archs/VC-WIN32/opensslconf.h
  /usr/local/include/node/openssl/archs/VC-WIN64A/opensslconf.h
  /usr/local/include/node/openssl/archs/aix-gcc/opensslconf.h
  /usr/local/include/node/openssl/archs/aix64-gcc/opensslconf.h
  /usr/local/include/node/openssl/archs/darwin-i386-cc/opensslconf.h
  /usr/local/include/node/openssl/archs/darwin64-x86_64-cc/opensslconf.h
  /usr/local/include/node/openssl/archs/linux-aarch64/opensslconf.h
  /usr/local/include/node/openssl/archs/linux-armv4/opensslconf.h
  /usr/local/include/node/openssl/archs/linux-elf/opensslconf.h
  /usr/local/include/node/openssl/archs/linux-ppc/opensslconf.h
  /usr/local/include/node/openssl/archs/linux-ppc64/opensslconf.h
  /usr/local/include/node/openssl/archs/linux-x32/opensslconf.h
  /usr/local/include/node/openssl/archs/linux-x86_64/opensslconf.h
  /usr/local/include/node/openssl/archs/linux32-s390x/opensslconf.h
  /usr/local/include/node/openssl/archs/linux64-s390x/opensslconf.h
  /usr/local/include/node/openssl/archs/solaris-x86-gcc/opensslconf.h
  /usr/local/include/node/openssl/archs/solaris64-x86_64-gcc/opensslconf.h
  /usr/local/include/node/openssl/des_old.h
  /usr/local/include/node/openssl/dso.h
  /usr/local/include/node/openssl/krb5_asn.h
  /usr/local/include/node/openssl/kssl.h
  /usr/local/include/node/openssl/pqueue.h
  /usr/local/include/node/openssl/ssl23.h
  /usr/local/include/node/openssl/ui_compat.h
  /usr/local/include/node/pthread-barrier.h
  /usr/local/include/node/pthread-fixes.h
  /usr/local/include/node/stdint-msvc2008.h
  /usr/local/include/node/tree.h
  /usr/local/include/node/uv-aix.h
  /usr/local/include/node/uv-bsd.h
  /usr/local/include/node/uv-darwin.h
  /usr/local/include/node/uv-errno.h
  /usr/local/include/node/uv-linux.h
  /usr/local/include/node/uv-sunos.h
  /usr/local/include/node/uv-threadpool.h
  /usr/local/include/node/uv-unix.h
  /usr/local/include/node/uv-version.h
  /usr/local/include/node/uv-win.h
  /usr/local/include/node/v8-debug.h
  /usr/local/include/node/v8-experimental.h

Warning: An outdated version (1.9.3) of Git was detected in your PATH.
Git 2.14.3 or newer is required for Homebrew.
Please upgrade:
  brew install git

Warning: Homebrew's sbin was not found in your PATH but you have installed
formulae that put executables in /usr/local/sbin.
Consider setting the PATH for example like so:
  echo 'export PATH="/usr/local/sbin:$PATH"' >> ~/.bash_profile

Warning: Your Xcode (6.1.1) is too outdated.
Please update to Xcode 10.1 (or delete it).
Xcode can be updated from the App Store.

Warning: Your Xcode (6.1.1) is outdated.
Please update to Xcode 10.1 (or delete it).
Xcode can be updated from the App Store.
$ echo 'export PATH="/usr/local/sbin:$PATH"' >> ~/.bash_profile
$ node -v
v12.12.0
$ npm -v
6.13.0

削除

pkgで入れた、Node.js と npm を削除

$ lsbom -f -l -s -pf /var/db/receipts/org.nodejs.node.pkg.bom | while read i; do  echo  ${i}; done | sed -e "s/^\.\///"

$ lsbom -f -l -s -pf /var/db/receipts/org.nodejs.node.pkg.bom | while read i; do  echo  ${i}; done | sed -e "s/^\.\///"

$ lsbom -f -l -s -pf /var/db/receipts/org.nodejs.node.pkg.bom | while read i; do  echo  ${i}; done | sed -e "s/^\.\///" | while read ii; do echo ${ii}; done

これで消えた

$ lsbom -f -l -s -pf /var/db/receipts/org.nodejs.node.pkg.bom | while read i; do  echo  ${i}; done | sed -e "s/^\.\///" | while read ii; do sudo rm /usr/local/${ii}
 done

残ったフォルダを削除

$ sudo rm -rf .npm
$ sudo rm -rf .npm-global

この辺りも削除

sudo rm -rf /usr/local/lib/node \
     /usr/local/lib/node_modules \
     /var/db/receipts/org.nodejs.*

brew list して node があったので、削除

brew uninstall node

再インストール

brew install nodebrew

nodebrew setup

Kazu-MacBook-Pro:~ kazoo$ brew install nodebrew
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
==> New Formulae
cf-tool
==> Updated Formulae
mysql ✔                   embulk                    kubectx                   qbs
ruby-build ✔              erlang                    kubernetes-helm           qscintilla2
abcm2ps                   exploitdb                 lazydocker                rancher-cli
abcmidi                   eye-d3                    lazygit                   rethinkdb
ansifilter                flow                      lego                      root
armadillo                 folly                     libheif                   scamper
assimp                    fop                       libimobiledevice          scc
aws-cdk                   fselect                   libplist                  sfst
aws-google-auth           futhark                   librealsense              sip
azure-cli                 gatsby-cli                mariadb-connector-c       sord
badtouch                  gifski                    mdbook                    sratoolkit
balena-cli                git-annex                 mkcert                    stanford-corenlp
bibutils                  git-archive-all           mkvtoolnix                starship
bitrise                   git-cola                  monero                    subversion
blast                     glooctl                   navi                      suil
broot                     goreleaser                neo4j                     sundials
bullet                    gradle                    neomutt                   swiftlint
bundletool                grpc                      netlify-cli               telegraf
byacc                     h3                        nlohmann-json             termshark
caf                       haxe                      now-cli                   terragrunt
carla                     hcloud                    octave                    tokei
cfn-lint                  highlight                 onefetch                  tunnel
cheat                     ios-webkit-debug-proxy    paket                     ungit
conan                     ipmiutil                  petsc                     urh
convox                    janet                     petsc-complex             vala
dartsim                   jenkins                   pioneer                   vfuse
dependency-check          jfrog-cli-go              plantuml                  vtk
deployer                  jhipster                  prettier                  wtf
devspace                  jo                        proj                      wtfutil
dive                      just                      pulumi                    xa
dvc                       kallisto                  pyqt                      yadm
==> Renamed Formulae
usbmuxd -> libusbmuxd
==> Deleted Formulae
pound                              raine                              wine

==> Downloading https://github.com/hokaccha/nodebrew/archive/v1.0.1.tar.gz
==> Downloading from https://codeload.github.com/hokaccha/nodebrew/tar.gz/v1.0.1
######################################################################## 100.0%
==> Caveats
You need to manually run setup_dirs to create directories required by nodebrew:
  /usr/local/opt/nodebrew/bin/nodebrew setup_dirs

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

To use Homebrew's directories rather than ~/.nodebrew add to your profile:
  export NODEBREW_ROOT=/usr/local/var/nodebrew

Bash completion has been installed to:
  /usr/local/etc/bash_completion.d

zsh completions have been installed to:
  /usr/local/share/zsh/site-functions
==> Summary
🍺  /usr/local/Cellar/nodebrew/1.0.1: 8 files, 38.6KB, built in 5 seconds
Kazu-MacBook-Pro:~ kazoo$ brew list
autoconf    gdbm        libunistring    nodebrew    rbenv       sqlite
aws-sam-cli gettext     makedepend  openssl     readline    tree
cmake       icu4c       mysql       pkg-config  ruby-build  wget
curl        libidn2     nkf     python      sphinx-doc  xz
$ nodebrew setup
Fetching nodebrew...
Installed nodebrew in $HOME/.nodebrew

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

~/.bash_profile にパス追加

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

nodeのインストール

$ nodebrew install-binary stable
Fetching: https://nodejs.org/dist/v12.13.0/node-v12.13.0-darwin-x64.tar.gz
######################################################################## 100.0%
Installed successfully
$ nodebrew list
v12.13.0
current: none
$ nodebrew use stable
use v12.13.0
$ nodebrew list
v12.13.0
current: v12.13.0
$ node -v
v12.13.0
$ npm -v
6.12.0
$ npm root -g
/Users/kazoo/.npm-global/lib/node_modules
$ which node
/Users/kazoo/.nodebrew/current/bin/node
$ npm update -g npm

参考リンク


「node.js」「npm」を使って「bootstrap」を導入するまでのまとめ

$
0
0

1. はじめに

1.1. 背景

「node.js」でSPA(シングル・ページ・アプリケーション)を作りたい。
それにあたって、「bootstrap」を使いたい!

1.2. 目的

「node.js」の「npm」を使って「bootstrap」を読み込むこと。

2. 「npm」で「bootstrap」の導入

早速、「npm」コマンドでbootstrapをインストールします。

2.1. 筆者の開発環境の確認

bootstrapの導入にはCDNを使うのが一番楽です。

(なんらかのHTMLにて)CDNでの導入
<!-- CSS only --><linkrel="stylesheet"href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"crossorigin="anonymous"><!-- JS, Popper.js, and jQuery --><script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"crossorigin="anonymous"></script>

bootstrap
BootstrapCDN

しかし、「npm」を使ってbootstrapを導入すると、後々、CSSのカスタムに便利らしいです。
なので、今回はそちらの手法を採用します。
「npm」を使うに当たって、PCの「node.js」「npm」のバージョンを確認します。

バージョンの確認
% node -v
v12.2.0
% npm -v
6.9.0

2.2. 「npm」を初期化する

node-js-bootstrapというフォルダを作成して、npm init --forceで、package.jsonを作ります。

新規プロジェクト用のフォルダを作成し、npmを初期化
% mkdir node-js-bootstrap
% cd node-js-bootstrap 
% npm init --force
npm WARN using --force I sure hope you know what you are doing.
Wrote to ~/node-js-bootstrap/package.json:

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

% ls
package.json

package.jsonが作られました。

2.3. 「npm」を使って「bootstrap」をインストールする

公式サイトに示されているように、npm install bootstrapを実行します。

npmでbootstrapをインストール
% npm i -S bootstrap 
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN bootstrap@4.4.1 requires a peer of jquery@1.9.1 - 3 but none is installed. You must install peer dependencies yourself.
npm WARN bootstrap@4.4.1 requires a peer of popper.js@^1.16.0 but none is installed. You must install peer dependencies yourself.
npm WARN node-js-bootstrap@1.0.0 No description
npm WARN node-js-bootstrap@1.0.0 No repository field.

+ bootstrap@4.4.1
added 1 package from 2 contributors and audited 1 package in 0.923s
found 0 vulnerabilities

jquerypopper.jsがないと怒られました。
こちらも導入しましょう。

npmによるjqueryとpopper.jsの導入
% npm i -S jquery popper.js
npm WARN node-js-bootstrap@1.0.0 No description
npm WARN node-js-bootstrap@1.0.0 No repository field.

+ popper.js@1.16.0
+ jquery@3.4.1
added 2 packages from 3 contributors and audited 3 packages in 0.979s
found 0 vulnerabilities

ここから先は、目的によって行うことが異なります。
今回は、CSSをカスタマイズできるようにするために「Webpack」を導入します。

3. 「webpack」を用いて「bootstrap」を読み込む

3.1. 「webpack」の引用

こちらのWEBサイトがとても参考になります。
このWEBページを参照して、今回必要になるWebpackを導入します。

webpackなどの導入
% npm i -D webpack webpack-cli css-loader node-sass sass-loader postcss-loader autoprefixer extract-text-webpack-plugin@next

> fsevents@1.2.11 install ~/node-js-bootstrap/node_modules/fsevents
> node-gyp rebuild

  SOLINK_MODULE(target) Release/.node
  CXX(target) Release/obj.target/fse/fsevents.o
  SOLINK_MODULE(target) Release/fse.node

> node-sass@4.13.0 install ~/node-js-bootstrap/node_modules/node-sass
> node scripts/install.js

Cached binary found at ~/.npm/node-sass/4.13.0/darwin-x64-72_binding.node

> node-sass@4.13.0 postinstall ~/node-js-bootstrap/node_modules/node-sass
> node scripts/build.js

Binary found at ~/node-js-bootstrap/node_modules/node-sass/vendor/darwin-x64-72/binding.node
Testing binary
Binary is fine
npm WARN sass-loader@8.0.0 requires a peer of sass@^1.3.0 but none is installed. You must install peer dependencies yourself.
npm WARN sass-loader@8.0.0 requires a peer of fibers@>= 3.1.0 but none is installed. You must install peer dependencies yourself.
npm WARN node-js-bootstrap@1.0.0 No description
npm WARN node-js-bootstrap@1.0.0 No repository field.

+ postcss-loader@3.0.0
+ css-loader@3.4.0
+ autoprefixer@9.7.3
+ sass-loader@8.0.0
+ webpack-cli@3.3.10
+ webpack@4.41.5
+ node-sass@4.13.0
+ extract-text-webpack-plugin@4.0.0-beta.0
added 639 packages from 338 contributors and audited 6056 packages in 17.326s
found 0 vulnerabilities

sass-loadersassfibersを欲しているようなので、これもインストールしておきましょう。

sassとfibersのインストール
% npm i -D sass fibers

> fibers@4.0.2 install ~/node-js-bootstrap/node_modules/fibers
> node build.js || nodejs build.js

`darwin-x64-72` exists; testing
Binary is fine; exiting
npm WARN node-js-bootstrap@1.0.0 No description
npm WARN node-js-bootstrap@1.0.0 No repository field.

+ fibers@4.0.2
+ sass@1.24.0
added 3 packages from 4 contributors and audited 8209 packages in 2.926s
found 0 vulnerabilities

また、ここで必要になるwebpack-cliですが、$PATHを正しく通していないと、「見つからないからインストールするよ」という無限ループになるかもしれません(私の環境ではなりました。)

に丁寧に解説されています。
私の場合は$PATHをとおして、次のところを見るようになりました。

webpack-cliの場所
% which webpack-cli
~/.nodebrew/current/bin/webpack-cli

ここに$PATHが通ればOKです。

3.2. webpackのconfigファイルの作成

まるまる引用で申し訳ないですが、こちらのファイルを参照して、webpack.config.jsを作成しましょう。

ics-creative「webpack.config.js」インターネット, https://github.com/ics-creative/170330_webpack/blob/master/tutorial-bootstrap-style-js/webpack.config.js(2020/01/01閲覧)

webpack.config.js(ics-creative)
constExtractTextPlugin=require('extract-text-webpack-plugin');module.exports={// モード値を production に設定すると最適化された状態で、// development に設定するとソースマップ有効でJSファイルが出力されるmode:'production',module:{rules:[{// 対象となるファイルの拡張子(scss)test:/\.scss$/,// Sassファイルの読み込みとコンパイルuse:ExtractTextPlugin.extract([// CSSをバンドルするための機能{loader:'css-loader',options:{// オプションでCSS内のurl()メソッドの取り込まないurl:false,// ソースマップの利用有無sourceMap:true,// Sass+PostCSSの場合は2を指定importLoaders:2},},// PostCSSのための設定{loader:'postcss-loader',options:{// PostCSS側でもソースマップを有効にするsourceMap:true,// ベンダープレフィックスを自動付与するplugins:()=>[require('autoprefixer')]},},// Sassをバンドルするための機能{loader:'sass-loader',options:{// ソースマップの利用有無sourceMap:true,}}]),},],},plugins:[newExtractTextPlugin('style.css'),],// source-map方式でないと、CSSの元ソースが追跡できないためdevtool:"source-map"};

現時点でのファイルはこういう配置になっているはずです。

% tree -L 1
.├── node_modules
├── package-lock.json
├── package.json
└── webpack.config.js

3.3. 「src」フォルダと「dist」フォルダの配置

webpackは、多くのファイルをまとめて整理してくれる素敵なユーティリティです。
そのため、多くのファイルが格納されている「src」フォルダと、それらのファイルを素敵に整理する「dist」フォルダを用意する必要があります。
それらのフォルダを作りましょう。

「src」「dist」フォルダの作成
% mkdir src dist && tree -L 1
.├── dist
├── node_modules
├── package-lock.json
├── package.json
├── src
└── webpack.config.js

3 directories, 3 files

3.4. 「index.js」の配置

こちらも、最新版で学ぶwebpack 4入門 Bootstrapをバンドルする方法の記事を丸パクリで、大変申し訳ございません。「src」配下に「index.js」と「index.scss」を保存します。

index.js
import"bootstrap";import"./index.scss";
index.scss
@import"~bootstrap/scss/bootstrap.scss";
.├── dist
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── index.js
│   └── index.scss
└── webpack.config.js

このように「src」の下に2つファイルが保存された形になるはずです。

3.5. 「webpack」の実行

では、早速「webpack」を実行しましょう。

webpackの実行
% webpack
Hash: 4fbcfa08db41b1bd330b
Version: webpack 4.41.5
Time: 4600ms
Built at: 2020-01-01 01:49:17
        Asset     Size  Chunks                   Chunk Names
      main.js  169 KiB       0  [emitted]        main
  main.js.map  778 KiB       0  [emitted] [dev]  main
    style.css  157 KiB       0  [emitted]        main
style.css.map  432 KiB       0  [emitted] [dev]  main
Entrypoint main [big] = main.js style.css main.js.map style.css.map
[0] ./src/index.js 42 bytes {0}[built]
[4] (webpack)/buildin/global.js 472 bytes {0}[built]
[5] ./src/index.scss 41 bytes [built]
    + 4 hidden modules

WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
  main (326 KiB)
      main.js
      style.css


WARNING in webpack performance recommendations: 
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/
Child extract-text-webpack-plugin node_modules/extract-text-webpack-plugin/dist node_modules/css-loader/dist/cjs.js??ref--4-1!node_modules/postcss-loader/src/index.js??ref--4-2!node_modules/sass-loader/dist/cjs.js??ref--4-3!src/index.scss:
    Entrypoint undefined = extract-text-webpack-plugin-output-filename
    [0] ./node_modules/css-loader/dist/cjs.js??ref--4-1!./node_modules/postcss-loader/src??ref--4-2!./node_modules/sass-loader/dist/cjs.js??ref--4-3!./src/index.scss 591 KiB {0}[built]
        + 1 hidden module

生成されたmain.jsファイルが、244KiBより大きいので注意されていますが、現時点では放置しておきましょう。

4. 「index.html」の作成

では、distディレクトリ以下に、簡単なindex.htmlを作成して、正しく動いているか確認します。

index.html
<!doctype html><htmllang="ja"><head><metacharset="utf-8"><metaname="viewport"content="width=device-width, initial-scale=1, shrink-to-fit=no"><linkrel="stylesheet"href="./style.css"/><title>Hello, world!</title></head><body><divclass="container"><h1>Hello, world!</h1><divclass="card text-white bg-primary"><divclass="card-body">カードのレイアウトを表示
        </div></div></div><script src="./main.js"></script></body></html>

正しくCSSが読まれていれば、下の画像のように表示されると思います。

2020-01-01 WEBページ例.png

一方、正しく読み込めていなければ、次のように悲しいページになるはずです。

2020-01-01 WEBページ例-2.png

4.2. index.scssの編集

では、webpackを導入した理由にあった、カスタマイズをさせてみたいと思います。
先に作成した「src」下の「index.scss」を書き換えます。

index.scss
$theme-colors:("primary":#b0c4de);@import"~bootstrap/scss/bootstrap.scss";

そして、webpackを実行すると、CSSが更新されます。
その結果、WEBページを更新すると、次のような画面に変わるでしょう。

2020-01-01 WEBページ例-3.png

SASSについては、公式サイト:Bootstrap テーマで丁寧に解説されているので参照すると良いでしょう。

4.3. Javascript の確認

CSSの確認ばかりしていて、JavaScriptが正しく動いているかを確認していませんでした。
簡単に確認してみましょう。

次の公式サイトから拾ってきたコードを、index.html<body>内のどこかに貼り付けます。

index.htmlに追記
<divclass="modal fade"id="exampleModal"tabindex="-1"role="dialog"aria-labelledby="exampleModalLabel"aria-hidden="true"><divclass="modal-dialog"role="document"><divclass="modal-content"><divclass="modal-header"><h5class="modal-title"id="exampleModalLabel">Modal title</h5><buttontype="button"class="close"data-dismiss="modal"aria-label="Close"><spanaria-hidden="true">&times;</span></button></div><divclass="modal-body">
        ...
      </div><divclass="modal-footer"><buttontype="button"class="btn btn-secondary"data-dismiss="modal">Close</button><buttontype="button"class="btn btn-primary">Save changes</button></div></div></div></div>

2020-01-01 WEBページ例-4.png

すると、正しく動いていることが確認できると思います。

5. おわりに

ここまで書いてきたことより、「node.js」「npm」を使って「bootstrap」を導入し、実際に動いている(読み込めている)様子を確認できたこと、また、SASSを編集して自作テーマを作ることができる可能性も見えたと思います。

しかし、これで終わりではありません。SASSを編集してオリジナルのテーマを作るだけならば、node.jsを動かさなくとも、rubyなどでもできるのですから。
これらを「React」「Express」などと組み合わせて動かすことができることで、素敵なSPAが作れることでしょう。気づきがあり次第、また、Qiitaに投稿したいと思います。

参考WEBサイト

EnterpriseEthereum/Kaleidoをローカルtruffleからアクセスする

$
0
0

What's this?

パーミッションドのブロックチェーン勉強中です。

BaaS色々使いたいですが如何せんお金がかかります。
そんな中、EthereumベースのBaaS「Kaleido」は永久無料プランがあるのでそれは使いやすい、ということで勉強しています。

あんまり情報ないですが、マルチリージョンとかにもできるしEnterpriseEthereumのBaaSとしてはかなり優秀なのでは、という印象です。
詳しくは下記をみてください。

「Kaleido」企業向けイーサリアム開発のフルスタックBaaSとは?

今回はコンソーシアムを立ててコントラクトをデプロイ、ローカルのアプリケーションからパーミッションドアクセスできるところまでを確認します。
ここまでできればDAppが作れるはずです。

環境

下記で検証

$ sw_vers
> ProductName:  Mac OS X
> ProductVersion:   10.14.5
> BuildVersion: 18F2058
$ node -v> v10.18.0
$ npm -v> 6.13.4

nodeのバージョンは一個前のLTS?であるv10.18.0でないと動作しません。
後続のtruffleboxのnpm installで死にます。
初めv12.13.1とかでやってると死んでました。
まだtruffleかweb3のサポートが追いついてないようです。

手順

Kaleidoのコンソーシアム作成

Kaleidoのコンソーシアム作成については、先人の記事を参考ください。
ぽちぽちやれば良い感じになるはずです。

Ethereumベースのブロックチェーンを構築できるKaleidoを触ってみた

truffle環境の構築

Kaleido.ioが下記にtrufflebox置いてくれているので、基本ここに準拠するだけです。

truffle-kaleido-box

下記コマンドがローカルでうまく機能するかどうかを確認しましょう。

mkdir truffle-kaleido-box
cd truffle-kaleido-box
npx truffle unbox kaleido-io/truffle-kaleido-box
truffle develop
# 以下、truffle console上で
compile
test
migrate

Kaleidoへの接続・コントラクトデプロイ

ローカルでうまく動作することが確認できれば、次はコントラクトをKaleidoにデプロイしてみます。
これもぶっちゃけtruffle-kaleido-boxの「Connect to Kaleido」に従うだけです。
デプロイしたいノードで「+ Connect Node」をクリック(下記ボタン)

image.png

「Native JSON/RPC」を選択

image.png

必要なCredentialsを選択or新規作成
入れたら下記をクリック

image.png

表示されている「App Credential」、「Connection Url」をtruffleのconfigに記述します。
truffle-config.jsを開いて下記を修正。

constappCred='yourappcred';// from application credential widgetconstconnectionURL='nodeConnectionURL';// without protocol (https://)

※作成したコンソーシアムがQuorumであれば、type: 'quorum'のコメントアウトを外しておく。

下記コマンドでコントラクトをデプロイします

npx truffle migrate --reset

デプロイに成功したら、コンソールにデプロイしたコントラクトのアドレスが出ています。

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.

Running migration: 1_initial_migration.js
Deploying Migrations...
Migrations: 0xf38a1E94A9F65118caa957D60806688397BD147c
Saving successful migration to network...
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying SimpleStorage...
SimpleStorage: 0xAAbDE02d23eD1Fe769d22e2666E1490dAb9F58B6 ←これ
Saving successful migration to network...
Saving artifacts...

KaleidoのBlockExplorer上で、デプロイしたコントラクトを確認できます。

デプロイしたコントラクトを触ってみる

truffle consoleから確認

下記コマンドでtruffle consoleへ

npx truffle console

console上で下記コマンドでデプロイ済のコントラクトを取得

letdeployedContract=awaitSimpleStorage.deployed()

処理実行に必要なアドレスを取得

letaccounts=awaitweb3.eth.getAccounts()

下記でコントラクトにアクセス、storedData変数に格納されている数値を確認できる。

deployedContract.contract.methods.storedData().call()>'(なんらかの数字)'

下記でstoreされている数値を更新する。

deployedContract.contract.methods.set([数字]).send({from:accounts[0]})// ex) >deployedContract.contract.methods.set(99999).send({from: accounts[0]})

もう一度値を確認すると、値が更新されていることがわかる。

deployedContract.contract.methods.storedData().call()>'(さっき指定した数字)'

※ちなみにRPC接続だけでなく、KaleidoではREST APIサーバーも置いてくれるので、
REST API経由でも今回デプロイしたコントラクトにアクセスできます。
デプロイしたアドレスを指定して簡単に実行できます。


以上です。

ここまでできれば、あとはコントラクトアクセスするようなDAppを作れるはずです。
他の参加者もノードをポチって追加して、Credentialsを提供するだけで良い感じにできる、はずです。
運用方針あってんのか、もうちょっとコントラクトをセキュアにするとかワークフロー的なこともできると思いますが、そこの作り込みは勉強中です。

重ねてですが、パーミッションドなEthereumを利用するのは、Kaleidoが一番軽い感じで良いのではないのでしょうか。
何より無料、これがすごい。(どうやって収益化してるんだろう、、、)

これでコンソーシアムでの検証をやってみたいと思います。

参考

Express (Node.js) の Graceful shutdown

$
0
0

基本的な実装の仕方と、実装した場合 / しなかった場合、で実際にどういう動作をするか〜、について書きます。

Linux, Node.js 12.13.0, での話だけをします。

Graceful shutdown ?

Express (Node.js) に限りませんが、Web サーバーを停止する際、クライアントから接続中のリクエスト (リクエスト受付してまだレスポンスしていない接続) はどうなるでしょうか?

Graceful shutdown とは一般的に以下の停止を指します。

  • 停止指示後に、新しい接続を受付しない
  • 残った処理中の接続が完了するのを待ってから、プロセスを安全に停止する

SIGNALs

そもそも Web サーバープロセスはどうやって停止するかというと、 SIGNALを用いて停止します。

具体的には下表の通り、コマンド等によって SIGNALを送信できます。

SIGNALkill commandLinux TerminalKubernetes実装依存説明
SIGHUPkill -SIGHUP {pid}----Yesプロセスが端末から切断された際のシグナル
SIGINTkill -SIGINT {pid}CTRL + C--Yes割り込み
SIGKILLkill -SIGKILL {pid}--SIGTERM 送信から30秒後に送信NOプロセスの実装に依存しないOSからの強制終了
SIGTERMkill {pid}--最初に送信Yesプロセスの終了

最近では、プロセスを Dockerコンテナ化して、 Kubernetes等のプラットフォーム上で動かす事が多いと思います。

例えば Kubernetesでは、プロセス (Dockerコンテナ) を終了する際は、まず SIGTERMが送信され、30秒待ってもプロセスが終了しない場合、最終的に SIGKILLが送信されます。

Kubernetes - Pods - Termination of Pods

実装例

URL /sleepと、 SIGNAL SIGTERMでの Graceful shutdown を Expressで実装しました。

index.js
constexpress=require('express');constSLEEP_MSEC=30*1000;constapp=express();app.get('/sleep',(req,res)=>{setTimeout(()=>res.send('OK'),SLEEP_MSEC);});constserver=app.listen(3000,()=>console.log('Example app listening on port 3000!'));process.on('SIGTERM',()=>{server.close(()=>{console.log('Process terminated.')})});

実行します。

$ node index.js

Example app listening on port 3000!

ブラウザで以下の URL にアクセスすると、30秒待たされた後 OKと表示されます。

[未実装で] 実際に停止してみる

まずは 未実装の状態でどう動作するか検証します。

Express サーバーを起動した後、ブラウザで http://localhost:3000/sleepにアクセスします。
まだリクエストの処理中に SIGNALを送信します。

(1) SIGHUP

シグナルを送信でプロセスが即座に停止。

kill-SIGHUP{pid}
Hangup: 1

(2) SIGINT

シグナルを送信でプロセスが即座に停止。

kill-SIGINT{pid}
(terminal 上の出力はなし)

(3) SIGKILL

シグナルを送信でプロセスが即座に停止。

kill-SIGKILL{pid}
Killed: 9

(4) SIGTERM

シグナルを送信でプロセスが即座に停止。

kill{pid}
Terminated: 15

[SIGTERM 実装で] 実際に停止してみる

実装していない SIGTERM以外のシグナルは省略。

(5) SIGTERM

ブラウザで http://localhost:3000/sleepにアクセスする。

応答がすぐには帰ってこず、ローディング状態になります。

シグナルを送信します。

kill{pid}

プロセスは実行されたまま、無反応。

(terminal 上の出力はなし)

ブラウザから新しいタブを開いて http://localhost:3000/sleepに追加でアクセスする。

TCP 接続が拒否され、ブラウザ上に「正常に接続できませんでした」と表示される。

1度目のブラウザからのリクエストが 30 秒後に正常に応答が返り、プロセスが終了する。

Process terminated.

GoogleカレンダーAPIを用いて予定への出欠登録を行う

$
0
0

はじめに

明けましておめでとうございます。早いもので令和2年(2020年)になりました。
なかなか投稿できておらず、久々の投稿となってしまいました。
2020年も気軽に投稿していければなと思っています。
今回は、前回の記事「GoogleカレンダーAPIを用いてカレンダー一覧を取得する」に続き、
GoogleカレンダーAPIに関する記事です。

やりたいこと

  • Googleカレンダーに登録されてされている予定に対して出席・欠席の登録をAPI経由で行う

私はこのAPIをAlexaスキルに組み込んで、共有カレンダーに対して音声経由で出欠登録できるようにしてみました。

実装

registEvent.js
'use strict';consturl=require('url');consthttps=require('https');constHTTP_RESPONSE_OK=200;constACCESS_TOKEN='API実行に必要なアクセストークン';letcalendarId='対象となるGoogleカレンダーのID';leteventId='出欠登録を行う予定のID';letapiUrl=`https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events/${eventId}`;// PATCH用オブジェクトletrequestBody=newObject();requestBody.attendees=[{'displayName':'表示名を設定','email':'メールアドレスを設定','responseStatus':'accepted',// 出席の場合(欠席の場合は'declined')}];// PATCH用JSONデータletpatchData=JSON.stringify(requestBody,null,2);// 実行URL、メソッドの準備letoptions=url.parse(apiUrl);options.method="PATCH";options.headers={"Authorization":"Bearer "+ACCESS_TOKEN,"Content-Type":"application/json","Content-Length":Buffer.byteLength(patchData),};// カレンダー一覧取得のためのAPIletoptions=url.parse("https://www.googleapis.com/calendar/v3/users/me/calendarList");options.method="GET";options.headers={"Authorization":"Bearer "+ACCESS_TOKEN,};// API問い合わせ開始と、コールバックの記載letreq=https.request(options,(res)=>{letresult;if(res.statusCode===HTTP_RESPONSE_OK){result=true;}else{result=false;}// 受信データに対する処理なし(定義が必須)res.on("data",(d)=>{// 処理なし});// 応答終了イベント処理res.on("end",()=>{if(result===true){console.log('出欠情報の更新に成功');}else{console.log('出欠情報の更新に失敗');}});});req.on("error",(e)=>{console.log('エラー発生');});// PATCH用データの送信req.write(patchData);// データ送信終了req.end();

おわりに

GoogleカレンダーAPIに関してはまた投稿するかもしれません。

Node.js イベントループ

$
0
0

Node.js とは

  • Node.jsはChromeのV8 JavaScriptエンジンをベースに作られたJavaScriptランタイムです。
  • Node.jsはevent-driven、non-blocking I/O modelデザインなので軽量で効率的です。

Blocking vs Non-Blocking

この二つの違いは、リソースをプロセスに集中すべきか否かです。

Blocking (synchronous)
このオペレーションを処理する場合は、そのプロセス処理を終了するまで次のオペレーションに移ることが出来ません。

例えば、お茶を飲むという行動はBlockingオペレーションです。

 1. カップを口までは運ぶ 2. お茶を飲む 3. カップをテーブルに置く 4. 完了

お茶を飲んでいる間は、お茶を飲むことに集中していると思います。

Non-Blocking (asynchronous)
このオペレーションを処理する場合は、とりあえずプロセス処理を開始します。
I/Oオペレーション等でリソースを使わない割に時間がかかりそうだったら、一旦そのスレッドを保留して、他のスレッドのプロセス処理に移行します。
保留していたプロセスの処理が完了した、良いタイミングでそのプロセスに戻ってくることが出来ます。

例えば、お茶を沸かすという行動はBlockingオペレーションです。

 1. お茶にお湯を注ぐ 2. 数分待つ 3. お茶をテーブルまで運ぶ 4. 完了

お茶の完成まで、お湯を注いでから数分は自由な時間が出来るます。
その間に、テーブルを片付けたり、テレビをつけたり出来るはずです。

JavaScriptはシングルスレッド言語

スレッドとは

CPUのコアが処理するプロセスのQueue的なものです。
CPUサイクル毎に処理できるスレッドの数は、物理的なコア数に比例しています。

よって、CPUのコア数が4つの場合は
CPUサイクル毎に4つのスレッドを処理することが可能です。

シングルスレッドとは 

マルチスレッドの対義です。
JavaScriptの場合は言語のデザイン上、複数のスレッドを管理する機能がありません。
なので、すべてのオペレーションは一つのスレッド上で管理されています。

Node.js = non-blocking I/O model

しかし、asyncオペレーションを行うのには、複数のスレッド管理が必要です。
ではどうやってNode.jsはasyncオペレーションを行うのでしょうか?
答えは Node.jsのコア、イベントループです。

イベントループ とは

JavaScriptがシングルスレッド言語であるにも関わらず、Node.jsが non-blocking I/O オペレーションを行える仕組みです。

大まかな仕組み

スレッドの管理をJavaScript自体で行わず、カーネルにお願いする感じです。
カーネルがnon-blocking I/Oオペレーションを完了すると、Node.jsに終わったよーと教えてくれます。
そしたら、Node.jsは対象のcallbackを実行します。

もう少し詳しく

Node.jsのプロセス開始と同時に、イベントループも開始します。

Capture.PNG

  • timers: このフェーズでは、setTimeout()setInterval()でスケジュールされたcallbackを実行します。
  • pending callbacks: 次のイタレーションの I/O callbacksを実行します。
  • idle, prepare: 内部ロジックのみで使用
  • poll: 新たな I/O イベントを取得; I/O関連のcallbacksを実行; 必要であればここでオペレーションのブロック。
  • check: setImmediate() callbacks はここで実行されます
  • close callbacks: socket.on('close', ...)のようなcallbackがここで実行されます。

参照

https://nodejs.org/de/docs/guides/blocking-vs-non-blocking/
https://nodejs.org/de/docs/guides/event-loop-timers-and-nexttick/
https://blog.logrocket.com/a-complete-guide-to-the-node-js-event-loop/

Reactプロジェクト環境の構築メモ

$
0
0

freeCodeCamp の How to set up & deploy your React app from scratch using Webpack and Babelより、余計なパッケージ等が多く付属されてしまう create-react-appとは別の方法でのセットアップだそうだ。

  1. nvm で node と npm のインストール
  2. プロジェクトの初期化
  3. npm パッケージのインストール
  4. package.json の設定
  5. webpack.config.js の設定
  6. rc ファイルの設定
  7. サイトのコンテンツの作成
  8. サーバー起動

1. nvm で node と npm をインストール

$ sudo wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash
$ exec bash
$ nvm --version
0.35.2
$ nvm install node
$ node -v
v13.5.0
$ npm -v
6.13.4

2. プロジェクトの初期化

$ mkdir my-react-project &&cd my-react-project
$ npm init -y

3. npm パッケージのインストール

React, Webpack, Babel, ESlint, Less, Prettierパッケージをインストール:

$ npm install--save-dev\
react react-dom \
webpack webpack-dev-server webpack-cli \
@babel/core @babel/preset-env @babel/preset-react babel-loader \
eslint eslint-loader babel-eslint eslint-config-react eslint-plugin-react \
less less-loader css-loader style-loader 
$ npm install--save-dev--save-exact prettier
$ npm install html-webpack-plugin -D

4. package.json の設定

package.json内の "scripts"に以下の様に追加

"scripts":{"test":"echo \"Error: no test specified\"&& exit 1","start":"webpack-dev-server --mode development","format":"prettier --write \"src/**/*.js\"","eslint-fix":"eslint --fix \"src/**/*.js\"","build":"webpack --mode production"}

5. webpack.config.js の設定

webpack.config.jsを作成、以下を記入:

constpath=require('path');constHtmlWebpackPlugin=require('html-webpack-plugin');module.exports={devtool:'inline-source-map',entry:'./src/index.js',output:{path:__dirname+'/build',publicPath:'./',filename:'bundle.js'},devServer:{contentBase:'./build',port:4000,writeToDisk:true,historyApiFallback:true,publicPath:'./'},module:{rules:[{test:/\.(js|jsx)$/,exclude:/node_modules/,use:['babel-loader','eslint-loader']},{test:/\.less$/,use:['style-loader','css-loader','less-loader',]}]},plugins:[newHtmlWebpackPlugin({template:path.resolve('./index.html'),}),]};

6. rc ファイルの設定

.babelrc:

{"presets":["@babel/preset-env","@babel/preset-react"]}

.eslintrc:

{"parser":"babel-eslint","extends":"react","env":{"browser":true,"node":true},"settings":{"react":{"version":"detect"}}}

.prettierrc:

{"semi":true,"singleQuote":true,"trailingComma":"es5"}

7. サイトのコンテンツの作成

以下ファイルとディレクトリを作る:

  • index.html
  • /src
  • /src/index.js
  • /src/style/
  • /src/style/main.less
  • /src/style/header.less

内容は以下の通りに:

index.html:

<!DOCTYPE html><htmllang="en"><head><metacharset="utf-8"><title>Hi There</title></head><body><divid="root"></div><script src="bundle.js"></script></body></html>

index.js:

importReactfrom"react";importReactDOMfrom"react-dom";import"./style/main.less";classWelcomeextendsReact.Component{render(){return<h1>HelloWorld!</h1>;
}};ReactDOM.render(<Welcome/>,document.getElementById("root"));

main.less:

@import"header.less";@color:#f5adad;body{background-color:@color;}

header.less:

.header{background-color:#3d3d;}

8. サーバー起動

Prettier と ESLint でコードを整形:

$ npm run format
$ npm run eslint-fix

サーバー起動:

$ npm run start

コンパイルが成功したら http://localhost:4000/に Hello World が表示されるはず。

暇すぎて画像ダウンロードツールを作ってみた話

$
0
0

年末年始暇ですね。

暇つぶしに何か作ろうと思ったんですが、何も思いつかず。。。
普段AngularやNestJSでTypescriptばっかり書いてますが、たまにはJavascriptでも書いてみようと思いました。

とりあえずWebページのスクレイピングってやったことないからJavascriptで書いてみるかーということでしょうもないツールを作ってみました。

作ったもの

WebページのURLを指定すると、そのページ内のimgタグの写真をすべてダウンロードするコマンドラインツールです
img-downloader

使い方

  • nodeコマンドでimg-downloader.jsを実行します
  • 引数に対象のWebページURLを指定します
npm install
node img-downloader.js {web-page-url}

結果

  • output/{YYYYMMDDhhmmss}ディレクトリに画像が出力されます

実行例

以下のページから画像を取得してみます
https://www.irasutoya.com/2018/12/blog-post_676.html

node img-downloader https://www.irasutoya.com/2018/12/blog-post_676.html

↓ 実行

image.png

 ↓ 出力

image.png

指定したページ内の全画像をとってくるので、ロゴやバナーの画像なんかの意図しない画像も取れてしまうのが難点です。。。

技術的な話

すごいシンプルなJavascriptで書いてます。
ほんとはスクレイピングも自前でやろうかと思ったんですが、めんどくさくなってscraperjs使っちゃいました。。

使ってるパッケージ

  • fs
    • ファイルの入出力用パッケージ
  • request
    • HTTPリクエスト用パッケージ
  • scraperjs
    • Webページのスクレイピング用パッケージ

スクレイピング

scraperjsを扱うためのUtilクラスを作りました。
getTargetImgURl()で指定URLからimgタグのsrc属性をピックアップします。

utils/scraper.util.js
classScraperUtil{getTargetImgUrl(pageUrl){returnthis.scrape(pageUrl,($)=>{return$('img').map(function(){return$(this).attr('src');}).get();});}scrape(pageUrl,fnScraping){returnnewPromise((resolve,reject)=>{scraperjs.StaticScraper.create(pageUrl).scrape(($)=>fnScraping($)).then(items=>resolve(items)).catch(err=>reject(err));});}}

簡単にスクレイピングできて良いですね。

画像のダウンロード

src属性に指定された値によってダウンロード方法が変わります

URLの場合

  • requestパッケージで画像データをGET
  • GETしたデータをfsパッケージのwriteFileSyncでファイル出力
    • ファイル名はURLから取得

BASE64データの場合

data:image/xxx~で始まる文字列の場合、BASE64データが指定されていると判断し、以下のように出力しています。

  • str.match()でdata:image/xxxx;base64,を抽出
  • 上記で抽出した文字列を除いた文字列(画像データ)をBASE64デコード
    • ブラウザ上でないとwindow.atobは使えないため、Bufferを使ってデコードしています
  • fsパッケージのwriteFileSyncでファイル出力
    • ファイル名はraw{cnt}.{type}で出力
    • typeは上記で抽出した文字列の画像タイプ(xxxxの部分)を指定

最後に

休みボケの中で適当に書いているため、突っ込みどころ満載なコードになってしまいましたが、良い暇つぶしになりました。

Typescriptだと型とかきっちりしてて書いてる途中でエラーに気付けますが、Javascriptだと実行してようやく気付くことが多いので、久々だと意外と難しいですね(笑)

(追記)
Googleの画像検索の結果一括保存したかったのにできてないので、気が向いたらアップデートしていこうかと思います。


比較的早く Svelte をセットアップする方法

DialogflowでGoogle Homeの挙動をカスタムする

$
0
0

目的

先日、YouTube Premium特典でGoogle Nest Miniを無料で頂いちゃいました!
早速ルンバやら電気やら色々連携して楽しんでいたのですが、やはり挙動をカスタムしてみたくなりまして...。
Dialogflowを使えばそこそこ簡単にできそうだったので、備忘録として残します。

注記:DialogflowはちょこちょこWebページのUIが変わるっぽくて、ネット上の記事を漁っていても全然違う画面で大変でした。これを読む頃にはまた変わっているかもしれません!

やりたいこと

2つのことをやってみたいと思います。

  • DialogflowのIntent機能を使って簡単なレスポンスを構築する
    こちらに関してはほぼコーディング不要です。

  • DialogflowのIntent機能+Webhookで簡単なレスポンスを構築する
    上とやりたいことは同じですが、あえてコードを書きます。これによって外部サービスを呼び出すなど、幅が広がるでしょう。

前提

  • Google Home(またはGoogle Home mini、Google Nest mini、Google Assistantアプリなど)があること
  • Google AssistantアプリでVoiceMatchが済んでいること これが終わっていないと、いくらGoogle Homeに話しかけても「話しかけている人のコンテンツ」であることがわからないのでうまく動きません。 それでもうまく行かない場合は、VoiceMatchをやり直してみると良いかもしれません。

DialogflowのIntent機能のみで簡単なレスポンスを構築する

Actions on Googleにアクセスしてプロジェクトを作成

名前の通りですが、カスタムアクションを定義するにはまずはここにアクセスしましょう。

初回ならログインを求められると思いますが、ココ超重要!です。
使いたいGoogle Homeにログインしているアカウントと同一のアカウントにしましょう。でないと色々破綻します。

image.png
早速「New Project」ボタンを押して、プロジェクト名と言語、地域を選択します。

テンプレを選ぶ

色々とテンプレが用意されていますが、今回は「Conversational」を使ってみます。
image.png

このアクションのトリガーを決める

下図の画面が出るので、「Decide how your Action is invoked」をクリックします。
image.png

「Display Name」部分がトリガーのキーワードになります。
下図の例だと、「OK Google、過去ガジェット研究所につないで」がトリガーになります。
image.png

※このDisplay Nameは、Actions directory(公開リポジトリ的な)内で一意である必要があるため、場合によっては既に取られてしまっている可能性があります。まあしょうがないですね。ちなみに今回はActions directoryに公開はしません。審査とかあるっぽいので。

右上の「Save」を押せば設定終了です。

Actionの作成

続いてActionを作成していきます。
左側の「Actions」から、今回は「Custom Intent」を選び、「Build」をクリックします。
image.png

するとDialogflowの画面が立ち上がるので、とりあえず言語を設定してから「Create」ボタンをクリックします。
image.png

この類の話だとかなり一般的な話ですが、こういった各種会話サービスは

  • ユーザからのリクエストをもらう
  • 意図を解釈する
  • レスポンスを決める
  • レスポンスを返す

必要があります。
今回、ユーザからのリクエストはGoogle Homeが受け取りますのであまりいじりません。
左側の「Intents」では、意図とレスポンスの設定ができるのでIntentsを見ていきます。

Intentsの設定

「Intents」から「Create Intent」ボタンをクリックします。
image.png

色々あってややこしいですが、とりあえず今回は2つだけ使います。
image.png

Training phrases

まずはTraining phrasesを展開し、「Add Training Phrases」をクリックします。
さてこのTraining phrasesですが、できるだけ正確な言葉で書いておくことで、実際のユーザのリクエストの文言が多少ぶれていたとしても、ある程度ユーザの意図をよしなに汲み取ってくれます。
image.png

たとえば今回は「お腹が空いた」としていますが、「お腹すいた」や「お腹ペコペコ」などでも同じIntentに誘導されます。
入力したらEnterキーを押します。

Response

続いてResponseを展開し、「Add Response」をクリックします。
複数入力しておくと、どれかをランダムに返す仕様です。
image.png

名前を決めて保存&簡易テスト

上部で名前を決め、「Save」ボタンで保存します。
image.png

一応Dialogflow単体でもテストができます。
画面右部の入力欄にテキストを入力すると、レスポンスが帰ってきます。

image.png

Integrationsの設定

ここでは、各種サービスとDialogflowの連携が設定できます。
左部のIntegrationsからGoogle Assistantの「Integration settings」をクリックします。
image.png

するとこんな画面が出てくるので、Add Intentをクリックして先程作ったIntentを選びましょう。
image.png

終わったらCloseボタンで閉じます。

テスト

テスト方法はいくつかありますが、Google Assistantアプリを使うか、Google Home本体を使います。
アプリまたはHomeが、上記Intentを作成したアカウントと同一であれば自動的に接続は済んでいます。
うまく行かない場合はアプリやHomeの再起動、またはVoiceMatchをやり直してください(私も何度かハマりました)。

アプリ、Homeいずれの場合でもこんな感じのやり取りになります。
ユーザ:「(OK Google)、過去ガジェット研究所につないで」
アプリまたはHome:「わかりました。過去ガジェット研究所のテストバージョンです。こんにちは!」
ユーザ:「お腹ペコペコ」
アプリまたはHome:「おじいちゃんご飯は昨日食べたでしょ」

ちなみにこのままだとずっと過去ガジェット研究所対話している状態になり、普段のGoogle Assistantが使えなくなってしまいますが、「キャンセル」と言えば会話から抜け出せます。この退出用キーワードもカスタム可能です。

DialogflowのIntent機能+Webhookで簡単なレスポンスを構築する

では、自分でコードを書いてカスタムしてみましょう。これで外部サービスのWeb APIを呼び出すなど、幅が広がるはずです。
ちなみに外部ネットワークへの通信についてはGoogle Cloud Platformの料金体系上、課金が発生するはずです。

Fulfillmentの設定

コードは、サーバレスでおなじみのCloud Functions on Firebaseで成り立っています。
画面上の簡易エディタもあるのでそこそこ便利です。

さて、先程のDialogflow画面のFulfillmentを選び、2番目の「Disabled」をクリックして有効化しましょう。
image.png

このページでやるなら言語環境はnode.jsになります。
ちなみにWebhookはココでやる必要はなくて、例えばCloud Functions本家を使ったりLambdaを使ったりすることは可能ですが、とりあえず手っ取り早いのでココを使います。

コードのカスタマイズとデプロイ

色々と書いてありますが、大事なのはほんの数行です。
下図はサンプルそのままですが、一部/* */で囲んだところを変更しています。

// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs// for Dialogflow fulfillment library docs, samples, and to report issues'use strict';constfunctions=require('firebase-functions');const{WebhookClient}=require('dialogflow-fulfillment');const{Card,Suggestion}=require('dialogflow-fulfillment');process.env.DEBUG='dialogflow:debug';// enables lib debugging statementsexports.dialogflowFirebaseFulfillment=functions.https.onRequest((request,response)=>{constagent=newWebhookClient({request,response});console.log('Dialogflow Request headers: '+JSON.stringify(request.headers));console.log('Dialogflow Request body: '+JSON.stringify(request.body));functionwelcome(agent){agent.add(`Welcome to my agent!`);}functionfallback(agent){agent.add(`I didn't understand`);agent.add(`I'm sorry, can you try again?`);}// // Uncomment and edit to make your own intent handler// // uncomment `intentMap.set('your intent name here', yourFunctionHandler);`// // below to get this function to be run when a Dialogflow intent is matched// function yourFunctionHandler(agent) {//   agent.add(`This message is from Dialogflow's Cloud Functions for Firebase editor!`);//   agent.add(new Card({//       title: `Title: this is a card title`,//       imageUrl: 'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png',//       text: `This is the body text of a card.  You can even use line\n  breaks and emoji! 💁`,//       buttonText: 'This is a button',//       buttonUrl: 'https://assistant.google.com/'//     })//   );//   agent.add(new Suggestion(`Quick Reply`));//   agent.add(new Suggestion(`Suggestion`));//   agent.setContext({ name: 'weather', lifespan: 2, parameters: { city: 'Rome' }});// }// // Uncomment and edit to make your own Google Assistant intent handler// // uncomment `intentMap.set('your intent name here', googleAssistantHandler);`// // below to get this function to be run when a Dialogflow intent is matched// function googleAssistantHandler(agent) {//   let conv = agent.conv(); // Get Actions on Google library conv instance//   conv.ask('Hello from the Actions on Google client library!') // Use Actions on Google library//   agent.add(conv); // Add Actions on Google library responses to your agent's response// }// // See https://github.com/dialogflow/fulfillment-actions-library-nodejs// // for a complete Dialogflow fulfillment library Actions on Google client library v2 integration sample/* 追加 腹減った系男子が応答する内容を定義する */functionhungryBoy(agent){agent.add('うるせえよ うるうるせえよ うるせえよ')}/* 追加ここまで */// Run the proper function handler based on the matched Dialogflow intent nameletintentMap=newMap();intentMap.set('Default Welcome Intent',welcome);intentMap.set('Default Fallback Intent',fallback);// intentMap.set('your intent name here', yourFunctionHandler);// intentMap.set('your intent name here', googleAssistantHandler);/* 追加 intentMap.set('先の手順で作成したIntentの名前', 動かしたい関数の名前)*/intentMap.set('腹減った系男子',hungryBoy);/* 追加ここまで */agent.handleRequest(intentMap);});

ということで以下を変更しています。

  • 関数1つの追加
  • Intentと関数を対応付ける1文を追加

変更が終わったら「Deploy」ボタンでデプロイしましょう。初回は2-3分かかると思います。

Intentsの設定

再びIntentsの設定に戻ります。
左部のIntentsボタンから、先に作っておいたIntentをクリックします。
image.png

まずは、デフォルトのレスポンスを消してしまいましょう。
image.png

次に、「Fulfillment」から「Enable Fulfillment」をクリックします。
image.png

最後に、「Enable webhook call for this intent」をONにしたら完了です。
image.png

Saveをクリックして終わりにしましょう。

テスト

先程と同じ手順で、Google HomeまたはGoogle Assistantアプリから挙動を確認します。

今回はやりとりはこんな感じになります。
ユーザ:「(OK Google)、過去ガジェット研究所につないで」
アプリまたはHome:「わかりました。過去ガジェット研究所のテストバージョンです。こんにちは!」
ユーザ:「お腹ペコペコ」
アプリまたはHome:「うるせえよ うるうるせえよ うるせえよ」

まとめ

Google Home単体で既に十分な機能を発揮してくれていますが、やはりデベロッパーたるものカスタマイズがしたくなるものです。
Dialogflowを使えばローコーディングでレスポンスを構築できますし、自分でコードを書くこともできます。
あとはEntityの設定やパラメータの受け取りなど、応用を利かせることも可能です。
こちらはそのうちまた記事を書きたいと思います。

Docker-composeを使ってnode.jsの環境構築をしてみたのよ。

$
0
0

Docker-composeを使ってnode.jsの環境構築をする。

こちらが前回の記事。
Dockerについて基本的なことなぞってみたのよ
こちらでDockerの基本的なことをなぞったので今回はdocker-composeを使っていこうと思います。

docker-composeとDockerfileどう違う?

docker-composeはdocker-compose.ymlの記述をdocker-composeコマンドを使ってdockerイメージを作成します。
Dockerfileはdocker buildコマンドでdockerイメージを作成します。

Dockerfile・・・docker image作成手順
docker compose・・・複数のコンテナのアプリケーション定義を記述でき、それぞれのコンテナのdocker imageを作成できる。

まずDockerの考え方として
messageImage_1577953535239.jpg

この考え方が定着してると理解しやすいのかなと思います。
まずはdocker imageを作成するのが先です。
以下のコマンドでdocker imageを作成することができます。
※認識不足だったら教えてください。

# docker hubからdocker imageをpullする
$ docker pull [IMAGE_NAME]

# 既存のdocker imageからオリジナルのdocker imageを作成する
$ docker commit [CREATE_IMAGE_NAME] [IMAGE_NAME]

# Dockerfileからイメージを作成
$ docker build -t[イメージに名前を設定] [IMAGE_NAME] [Dockerfile/path]

# docker-compose.ymlからイメージを作成
$ docker-compose build

その後docker containerを起動します。
その時のコマンドがこちら

# 単発での起動
$ docker run

# docker-composeでの起動
$ docker-compose up

imageがあってのcontainerと言う考えができたので、早速docker composeを使っていきます。

docker composeを使う。

おきまりのバージョン確認から。

$ docker-compose -v
docker-compose version 1.24.1, build 4667896b

OKですね。

今回はnode.jsを取り入れていきます。
参考にした記事はこちらです。
【初心者向け】Dockerで手軽にNode.js開発環境構築 (2)

初期のディレクトリ構成はこちら。

MyApp
├─ node   
│   ├─ Dockerfile
│   └─ .dockerignore
└─ docker-compose.yml

Dockerfileに記述していきます。

# ベースイメージを指定
FROM node

# node.js の環境変数を定義する
# 本番環境では production
ENV NODE_ENV=development

# 雛形を生成するのに必要なパッケージのインストール
RUN npm install -g express-generator

# ディレクトリを移動する
WORKDIR /app

# ポート3000番を開放する
EXPOSE 3000

次はdocker-compose.yml

version: '3'
services:
  webserver:
    build: node
    image: node-express-dev:1.0
    tty: true
    volumes:
      - ./node/app:/app
    ports:
      - "8080:3000"

このbuildと言う部分に注目。
ここにはDockerfileのパスを書きます。
つまりnodeディレクトリの中にあるDockerfileを使ってdocker imageを作成しますよと言う意味なんですね。

なんとなくDockerfileとdocker-composeの関係性が分かったかと思います。

Dockerfileとdocker-compose.ymlの記述が済んだらdocker imageを作成します。

$ docker-compose build
Step 1/5 : FROM node
(省略)
Successfully built c3154d2017fb
Successfully tagged node-express-dev:1.0

と表示されるかと思います。
結局のところ、docker-compoesと言ってもDockerfileからdocker imageを作成していたんですね。
なので、docker-compose.ymlを使うと依存関係なども定義しつつ、複数コンテナのDockerfileを実行できると言った利点があると言ったところでしょうか。

次行きます。

docker imagesで先ほどのイメージが作成されてるのを確認したらコンテナを起動させます。

$ docker-compose up -d

docker psでコンテナが起動している確認します。
起動していたらログインします。

$ docker exec -it [CONTAINER_NAME] /bin/sh

express-generator で雛形を作成します。

$ express -f --view=pug /app

npmをインストール

$ npm install

サーバー立ち上げ

$ npm start

ブラウザで http://localhost:8080にアクセスします。

「Express
Welcome to Express」

と表示されればOKです。

以上。

まとめ

docker runだったり、docker-compose upだったり似たようなコマンドがいっぱいあるので最初は戸惑いますが、やってることはイメージ作成→コンテ起動だけです。

その方法がたくさんあるだけです。

imageはhubからpullしてくるのか、runでイメージを自動ダウンロードするのか、はたまた既存のイメージを使ってdocker commitでオリジナルイメージを作成するのか、Dockerfileを使ってコードとしてイメージ作成を管理するのか。

コンテナ起動も一緒です。
docker runで取得したイメージでコンテナを作成するのか、docker-compseコマンドでdocker-compose.ymlを実行してコンテを作成するのか。

イメージ作成→コンテ起動

これをどのように実現するかだけです。
それでも各オプションでまた挙動は違うので、そこは随時調べながらやっていこうと思います!

RTA in Japan 2019のNodeCGレイアウトを動かしてみる

$
0
0

はじめに

RTA in JapanのNodeCGレイアウトを動かしてみるで起動までの道のりを解説しましたが、RTA in Japan 2019 Online以降のバージョンでセットアップ手順が変わったのでその解説です。
なお、差分以外の説明は適当なので前回の記事も参照してください。

この記事の目標

ローカル環境でRTA in Japan 2019のNodeCGレイアウトを動作させる。

事前に用意するもの

前と同じなので省略。Node.jsとyarnくらいは入れておいてください。

gitからダウンロード

コンソールで以下を実行。

git clone https://github.com/RTAinJapan/rtainjapan-layouts.git

git入れてない人はZIPファイルで直接ダウンロードしてもよいです。
git.png

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

NodeCG開発者のたゆまぬ努力により、パッケージインストールは以下の1回だけで済むようになりました。

cd rtainjapan-layouts
yarn

環境変数の設定

AdobeのWebフォントを利用しています。
環境変数として以下を設定してください。
TYPEKIT_ID=利用したいフォントのID
Adobeと契約していない場合は何でもいいので適当な値を設定してください。
Webフォントの読み込みに失敗しますが、一応使えます。

下記画像の塗りつぶしてる箇所が該当のID。
Adobe Font

アカウントに合わせたソース修正

自分のチャンネルに合わせてTwitchIDと配信タイトルのPrefixを設定します。

/src/extension/twitch.ts
constOUR_CHANNEL='pastan04';constnewTitle=`RTA in Japan 2019サンプル: ${newRun.title}`;

ビルド

yarn prod

この時TYPEKIT_IDの環境変数が読み込めていないエラーが出た場合は、環境変数が正しく設定されているか確認してみてください。
環境変数の反映には、コンソールの再起動等が必要になる場合があります。

Google APIの用意

前回と同じなので省略。

スプレッドシートの準備

文で説明するのはつらいのでキャプチャ画像で察してください。
1つのスプレッドシートを共有状態にし、以下3種のシートを用意してください。

ゲーム

ゲームシート

走者

走者シート

解説

解説シート

Configの用意

NodeCGのConfig

前回の説明では無くてもよいと書いていましたが、なるべくならあった方がよいので記載しておきます。
必要最小限の設定のみ記載していますが、全体を見たい方はこちらを参照してください。

.nodecg/cfg/nodecg.json
{"login":{"enabled":true,"sessionSecret":"saltだから適当な値でよいのだ","twitch":{"enabled":true,"clientID":"TwitchAPIのclientID","clientSecret":"TwitchAPIのclientSecret","scope":"channel_editor","allowedUsernames":["ログインを許容するTwitchユーザID"]}}}

Twitchログインの設定をすると、NodeCGでゲームを切り替えた時に自動でチャンネル情報の書き換えが行われます。

レイアウト固有の設定

前回からちょっと構造が変わりました。

.nodecg/cfg/rtainjapan-layouts.json
{"twitter":{"targetWords":["検索ワード。RTAinJapanとか"],"consumerKey":"TwitterAPIのConsumer Key","consumerSecret":"TwitterAPIのConsumer Secret","accessToken":"TwitterAPIのAccessToken","accessTokenSecret":"TwitterAPIのAccessToken Secret"},"spotify":{"clientId":"SpotifyAPIのClient ID","clientSecret":"SpotifyAPIのClient Secret"},"spreadsheetId":"スプレッドシートのURLのうち、/d/の後ろから末尾の/までのめちゃ長い英数記号文字","googleApiKey":"APIキー"}

それぞれのキー取得方法についてはあまり細かくは解説しないですが、どこの設定値かはわかるように各サービスのキャプチャを貼っておきます。

Twitch

それぞれ塗りつぶしているところ。
TwitchAPIの設定値
リダイレクトURLはhttp://[ホスト名]/login/auth/twitch

Twitter

それぞれ塗りつぶしているところ。
TwitterAPIの設定値

Google API

塗りつぶしているところのうち、上のAPIキーの方。
GoogleAPIの設定値

Spotify

オレンジで塗りつぶしているところ。
SpotifyAPIの設定値
リダイレクトURLはhttp://[ホスト名]/bundles/rtainjapan-layouts/spotify-callback/index.html

起動

yarn start
info: [nodecg/lib/server] NodeCG running on http://localhost:9090

上記のようなメッセージが表示されたら無事起動です。
メッセージに従い、ブラウザでアクセスしてみましょう。

アクセス

http://localhost:9090/を表示します。

TwitchのAPI設定をしている場合は以下が表示されます。
画面に従ってログインしてください。
Twitchログイン

TwitchのAPI設定をしていない場合、またはTwitchログインが完了した後は以下が表示されます。
NodeCGダッシュボード

Spotifyにログイン

2-MISCからログインします。画面に出た通りポチポチすればOKです。
NodeCG Spotify

動作確認

セットアップ画面

まずはセットアップ中のレイアウトを見てみましょう。
NodeCGダッシュボードから、GRAPHICS > BREAK-2.HTML

NodeCG break2

白抜きの箇所はHTML的に空白の領域で、下位レイヤの要素が映るようになっています。
RTA in Japan 2019を視聴されていた方はピンときたかもしれませんが、レイアウトの下位レイヤで動画を流すことによりRTAちゃんを動かしていました。

今回追加されたゲームレイアウト

QWOPはアスペクト比が特殊なため、専用のレイアウトが用意されています。
NodeCGダッシュボードから、GRAPHICS > QWOP.HTML

NodeCG qwop

OBSの設定

レイアウト登録

OBSのソース > 右クリ > 追加 > ブラウザを選択し、表示したいレイアウトのURLを入力します。
レイアウトのURLはダッシュボードで開いた時のものをそのまま入力すればよいです。
ローカルで起動しているNodeCGを参照する場合は以下となります。
http://localhost:9090/bundles/rtainjapan-layouts/graphics/qwop.html

TwitchAPIの設定をしている場合は、URLの末尾にkey情報が追加されるので、そこまで含めて登録します。
http://localhost:9090/bundles/rtainjapan-layouts/graphics/qwop.html?key=なんかめちゃ長いkey

OBS source

表示されていないときにソースをシャットダウンはチェックを入れるようにしましょう。
これを入れていないと、ソースを多数登録した時にムチャクチャ重くて画像等が上手く表示できないという事象がイベント中に発生しました。(裏話)

ゲーム設定

レイアウトの下に表示したいキャプチャデバイスなどのソースを登録してください。
あとは適当にクロップして黒枠のところに配置したら完成です。
OBS source game

★おしまい★

[初心者向け]IBM Cloud のKubernetesクラスタにNode.jsアプリをデプロイ

$
0
0

はじめに

年が明けまして2020年を迎えましたが、皆様いかがお過ごしでしょうか。

2020年代は5Gの整備が完了し、IoTや自動運転技術が大いに浸透する10年であると言われていますが、2010年代も激動の10年でした。
特に、2013年ごろから開発が始まったKubernetesがようやくGA段階となり、各クラウドベンダーでマネージドサービスとして展開されたことが大きかったと思います。
これによってコンテナ技術やマイクロサービスがより管理しやすくなり、これから様々なサービスが生まれ進化してくことでしょう。

今回は、初投稿ではありながら、これからKubernetesを触ってみたい!という方のために、私がいろんな文献や技術書を読み漁って勉強した、

Kubernetesでアプリケーションを外部公開する手順

を、IBM Cloudを用いて紹介しようと思います。

※なお、筆者も勉強し始めで、詳しい技術や論理は理解している途中でございますので、深く知りたい!という方はこの後の参考資料をご覧ください。
この記事はあくまでアプリを開発してKubernetesにデプロイするまでの流れを紹介するものです。

Kubernetesとは?

Kubernetesロゴ

1. 読み方

そもそもどう読むの?くべ..くば..くばねってす?何語?ってなると思います。私もなりました。
いろんな動画を見ていると開発者の間ではクーバネ(ー)ティスと呼ばれているみたいです。クーバネティス。分かんねぇよ。
ギリシャ語で操舵手とか、航海長とかいう意味みたいですね。
では一体どういう技術何でしょうか??

2. Kubernetesとは

Wikipediaによれば
コンテナ化したアプリケーションのデプロイ、スケーリング、および管理を行うための、オープンソースのコンテナオーケストレーションシステム
とされています。

私が一番にお世話になった技術書(詳しくは後述の参考資料をご覧ください)にも
コンテナ化されたアプリケーションを合理的に運用するために設計されたOSSのプラットフォーム
として紹介されています。

すなわち、KubernetesはDockerなどでコンテナ化されたアプリケーションなどを運用、管理するために利用されるOSSのプラットフォームということですね。なんかすごそうですね。

それでは早速本題に入っていきます。

開発環境

macOS
node.js 12.2.0
npm 6.13.4
docker cli 19.03.5

前提条件

  1. Node.js, npmがインストール済みである。
  2. Gitコマンドが打てる。
  3. Dockerアカウントを作成済みである。

1. 事前準備

1-1. IBM Cloudのアカウントを作成

まずはじめに、IBM Cloudのアカウントを作成しましょう。

Kubernetesの利用方法は数多くありますが、クラウドベンダーが提供するKubernetesマネージドサービスを利用すれば、必要なものが全て揃っているのでとても便利です。
これを使う手はありませんね。

ここで注意ですが、IBM CloudのKubernetesサービス(以下、IKS)は、無料アカウントでは利用できず、クレジットカードの登録をして、従量課金アカウントへアップグレードしなくてはいけません
しかし、だからと言ってお金がかかる訳ではなく、1ヶ月間限定やワーカノードは1つのみなどの色々な制約はありますが、IKSにはフリークラスタの提供があり、Kubernetesを試すことは十分にできますのでご安心ください!

サークルの仮入部みたいな感じですね。
やべ、Kubernetesサークル、ちょうたのちぃ...となれば会費払って正式に入部すればいい訳です。

まずはここにアクセスして、IBM Cloudのアカウント作成ページに飛んでください。

順調にアカウント作成が進んだら以下の画面に進むと思います。

コンソール画面.png

ここから、右上にあるアカウントのアップグレードをポチりしてください。
必要な情報を入力して、I accept the Cloud Service termにチェックを入れたら完了です。はい、早い。

このダッシュボード画面はあとで操作するのでこのままにしておいてください。

1-2. IBM Cloud CLIのインストール

続いてIBM Cloud CLIをインストールしましょう。

これは、ご自身のPCのターミナルやコマンドプロンプトでIBM Cloudのリソースを管理することができるコマンドラインインタフェースであり、同時にKubernetes CLIもインストールされるため、Kubernetesの操作がコマンドで可能になります。
基本的にこの先Kubernetesをコマンドで操作していくことになるので是非ともIBM Cloud CLIのインストールを完了させておいてください。

私はすでにインストールを済ませてあるのでインストールコマンドだけ載せておきます。(詳しくはこちらをご覧ください。)

Terminal
#Mac, Linux用$ curl -sL https://ibm.biz/idt-installer | bash

環境によってはインストールにかなり時間がかかると思います。

Terminal
$ ibmcloud -v
ibmcloud version 0.21.0+f5d1134-2019-12-16T07:34:45+00:00
Terminal
$ kubectl version --short
Client Version: v1.14.8

上記2つのコマンドを叩いてバージョンがうまく出ていればインストールは完了です!

2. Webアプリケーションを作成

2-1. Node.jsでアプリケーションを作成

事前準備が済んだら、早速デプロイするためのアプリケーションを作りましょう!
今回はGitHubに演習用の簡単なアプリケーションを作成しておきました。
是非使ってみてください。
Node.jsのアプリケーションです。
※Node.jsとnpmはインストール済みであることが前提条件です。

Terminal
$ git clone https://github.com/rikkyrice/kube-sample-app
Cloning into 'kube-sample-app'...
remote: Enumerating objects: 14, done.
remote: Counting objects: 100% (14/14), done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 14 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (14/14), done.

これで簡単にkube-sample-appというNode.jsアプリケーションのディレクトリが作成されました。
アプリケーション自体はいたってシンプルで、以下のようなサイトが表示されます。

0.0.0.0-8080.png

これでWebアプリケーションの作成は終了です。はやっ

3. Dockerコンテナを作成する

続いてDockerにて生成したアプリケーションのコンテナを作成します。
Dockerはインストール済みであることが前提です。

3-1. Dockerfileの作成

それでは、コンテナ化する際に必要になるDockerfileを作成しましょう。
Dockerfileにはコンテナを動作させる構成情報を記載していきます。
kube-sample-appをGitHubからcloneしてきた人はすでにディレクトリにDockerfileがあると思います。

Dockerfile
FROM node:12WORKDIR /usr/src/appCOPY package*.json ./RUN npm installCOPY . .EXPOSE 8080CMD ["node", "app.js"]

3-2. コンテナのイメージを作成し、DockerHubへpush

続いてTerminalにて、kube-sample-appディレクトリのファイルからコンテナのイメージを作成します。
以下のコマンドをkube-sample-appディレクトリ上で行ってください。
${ユーザ名}にはあなたのDockerHubのユーザ名を入力してください。
私の場合はrikkyrice/kube-sample-app:0.1となります。

Terminal
$ docker build --tag${ユーザ名}/kube-sample-app:0.1 .
Sending build context to Docker daemon    106kB
Step 1/7 : FROM node:12
 ---> 6b5991bf650f
Step 2/7 : WORKDIR /usr/src/app
 ---> Running in 02ac3c501da8
Removing intermediate container 02ac3c501da8
 ---> 7e8b2f94b323
Step 3/7 : COPY package*.json ./
 ---> 90cd726f65d3
Step 4/7 : RUN npm install---> Running in 3abe5e4c5677

> ejs@3.0.1 postinstall /usr/src/app/node_modules/ejs
> node ./postinstall.js

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

added 64 packages from 39 contributors and audited 139 packages in 3.197s
found 2 moderate severity vulnerabilities
  run `npm audit fix` to fix them, or `npm audit`for details
Removing intermediate container 3abe5e4c5677
 ---> 6882306b5a87
Step 5/7 : COPY ..---> 4085a1edc12c
Step 6/7 : EXPOSE 8080
 ---> Running in 794847f8f427
Removing intermediate container 794847f8f427
 ---> f8147522bc79
Step 7/7 : CMD ["node", "app.js"]---> Running in dadc7ad0b91e
Removing intermediate container dadc7ad0b91e
 ---> 1d08c3fe706c
Successfully built 1d08c3fe706c
Successfully tagged ${ユーザ名}/kube-sample-app:0.1

これでDockerイメージが${ユーザ名}/kube-sample-app:0.1という名前で作成され、ローカルリポジトリへ保存されました。
そこからDockerにログインし、ご自身のDockerHubのリモートリポジトリへイメージをpushします。
以下のコマンドを実行してください。

Terminal
$ docker login
Authenticating with existing credentials...
Login Succeeded
$ docker push ${ユーザ名}/kube-sample-app:0.1
The push refers to repository [docker.io/${ユーザ名}/kube-sample-app]
485558bdae5c: Pushed 
fe34ac93a512: Pushed 
91da8ffeb073: Pushed 
e204a1f85920: Pushed 
af817f759558: Pushed 
b5aa4fb76f12: Pushed 
f68782bbfa17: Pushed 
42f9c2f9c08e: Pushed 
99e8bd3efaaf: Pushed 
bee1e39d7c3a: Pushed 
1f59a4b2e206: Pushed 
0ca7f54856c0: Pushed 
ebb9ae013834: Pushed 
0.1: digest: sha256:15270113aa252fedf81d41066dd21a2cd7143fed7dc27649971bd9a6f2901ded size: 3051

これでご自身のDockerHubへイメージが格納されました。

いよいよKubernetesを触っていきます!

4. Kubernetesクラスタの作成

それではKubernetesのクラスタを作成しましょう。
基本的にアプリケーションはこのKubernetesクラスタのワーカノードが実行していきます。

4-1. IBM CloudからKubernetesクラスタを作成

先ほど事前準備で行ったIBM Cloudのアカウントダッシュボードを再度開いてください。

ダッシュボード.png

この右上にあるリソースの作成またはカタログを押すと、IBM Cloudのサービスを選択できる画面に進みます。
そこからカタログの検索kubernetesと入力し、Kubernetes Serviceを選択しましょう。

カタログ-kubernetes.png

画面が移動したらそのままCreateを押しましょう。

Kubernetes説明.png

クラスタの作成画面に移ります。
初期設定ではStandardが選択されていると思いますが、Freeを選択しましょう。
アカウントごとに一意の名前を自由に決められますが、今回はmyclusterとしておきます。
Create clusterを押してKubernetesクラスタを作成します!

cluster作成.png

Kubernetesクラスタの作成は約10分ほどかかります。
正月に余ったお餅でも食べながら気長に待ちましょう。

これでクラスタの作成は終了です。
次からはついにコンテナをクラスタへデプロイしていきます!

5. Kubernetesへコンテナをデプロイ

さぁついに最終章です。
Kubernetesにコンテナをデプロイしていきます!

5-1. ibmcloudへログイン

まずはターミナルを開いて、ibmcloudへログインしてください。

Terminal
$ ibmcloud login

このコマンドを叩くと、対話型のログインプロセスが起動します。
事前準備で作成したご自身のIBM Cloudアカウントのメールアドレスとパスワードを入力して、ログインを完了させてください。
OKと出れば、ログイン成功です。

5-2. Kubernetesをターミナルで操作

これから、IBM Cloudのダッシュボードで作成したKubernetesクラスタをターミナルで操作していくための準備をしていきます。
まず、作成したIBM CloudのKubernetesクラスタの画面のAccessタブを開き、次にAfter your cluster provisions, gain accessを順に実行していきます。

clusterダッシュボード.png

ターミナルを開いて、以下のコードを叩いてください。
Kubernetesクラスタの画面のコードをコピペすれば大丈夫です。

Terminal
$ ibmcloud ks cluster config --cluster${クラスタid}

正常に動作すると、環境変数が吐き出されると思います。
これをexportします。

Terminal
OK
mycluster の構成は正常にダウンロードされました。

環境変数をエクスポートして Kubernetes の使用を開始してください。

export KUBECONFIG=/Users/hashikiriku/.bluemix/plugins/container-service/clusters/mycluster/kube-config-hou02-mycluster.yml

$ export KUBECONFIG=/Users/hashikiriku/.bluemix/plugins/container-service/clusters/mycluster/kube-config-hou02-mycluster.yml

これで、指定したKubernetesクラスタを端末で操作することができるようになりました!
試しに以下のコマンドを叩いてみましょう。

Terminal
$ kubectl get node
NAME            STATUS   ROLES    AGE     VERSION
10.76.215.133   Ready    <none>   6m45s   v1.14.9+IKS

うまくワーカノードが表示されていますね。

5-3. deployment.ymlとservice.ymlを作成、実行

さぁ、Kubernetesクラスタにアクセスできるようになったので、いよいよコンテナをデプロイしていきます。

コンテナをデプロイするためにはポッドを作成し、そこにコンテナを配置する必要があります。
そのポッドを作成したり、稼働数を管理するのに用いられるのがデプロイメントです。
今回はこのデプロイメントをdeployment.ymlというマニフェストファイルに記述していきます。
GitHubからkube-sample-appをcloneしてきた方はすでにディレクトリに存在していると思います。
deployment.ymlのspec.template.spec.containers[1].imageの${ユーザ名}は、ご自身のDockerHubアカウントのユーザ名に書き換えてください。

deployment.yml
apiVersion:apps/v1kind:Deploymentmetadata:name:kube-sample-appspec:replicas:5selector:matchLabels:app:kube-sample-apptemplate:metadata:labels:app:kube-sample-appspec:containers:-name:kube-sample-appimage:${ユーザ名}/kube-sample-app:0.1command:ports:-containerPort:8080

これをターミナルで実行します。

Terminal
$ kubectl apply -f deployment.yml
deployment.apps/kube-sample-app created

これでポッドが作成されました。
今回はreplicasを5と設定し、稼働するポッドを5個生成するようにしました。
試しにポッドが5個作動しているか見てみます。

Terminal
$ kubectl get pod
NAME                              READY   STATUS    RESTARTS   AGE
kube-sample-app-f6478fbcc-74s4t   1/1     Running   0          3m18s
kube-sample-app-f6478fbcc-bbncl   1/1     Running   0          3m18s
kube-sample-app-f6478fbcc-lgk79   1/1     Running   0          3m18s
kube-sample-app-f6478fbcc-tplh6   1/1     Running   0          3m18s
kube-sample-app-f6478fbcc-znxq5   1/1     Running   0          3m18s

うまく動いているのがわかります。

しかし、これだけではブラウザからアクセスできません。
そこで、クライアントからのリクエストを受けるために必要なのがサービスです。
サービスにはいくつかタイプがあるのですが、今回はNodePortというタイプを用いて、ノードのIPアドレスに公開用ポート番号を開きます。
これで外部クライアントもポッドへアクセスすることができます。
今回はservice.ymlに設定を書き込んでいます。

service.yml
kind:ServiceapiVersion:v1metadata:name:kube-sample-app-servicespec:type:NodePortselector:app:kube-sample-appports:-name:webserverprotocol:TCPport:8080

これをターミナルで実行します。

Terminal
$ kubectl apply -f service.yml
service/kube-sample-app-service created

これで外部ネットワークへ公開する準備が整いました。
アクセスURLはKubernetesクラスタが持つパブリックIPアドレスに公開用ポートが付与された形で提供されています。
KubernetesクラスタのパブリックIPアドレスはIBM CloudのKubernetesクラスタの管理画面でも確認できますが、以下のコマンドでも確認することができます。

Terminal
$ ibmcloud ks workers ${クラスタ名}
OK
ID                                                     パブリック IP       プライベート IP      フレーバー    状態     状況     ゾーン     バージョン   
kube-bo6of95d0e80ipchv6ag-mycluster-default-000000a8   184.***.***.**   10.76.215.133     free       normal   Ready   hou02    1.14.9_1544   

このパブリック IPというのがKubernetesクラスタのパブリックIPアドレスになります。
さらに以下のコマンドで、サービスがどの公開用ポートで開いているかを確認することができます。

Terminal
$ kubectl get service kube-sample-app-service
NAME                      TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kube-sample-app-service   NodePort   172.21.213.190   <none>        8080:32175/TCP   28s

このPORT(S)にある、32175というのが公開用ポート番号です。
これで公開されているURLがわかりました。
私の環境ではhttp://184.***.***.**:32175/でアプリケーションが公開されています。
早速ブラウザで動作を確認してみましょう。

ブラウザ-アプリ確認.png

うまくアクセスできましたね!

他にもKubernetesには様々な機能があります。
例えばイングレスという機能を使えば、公開用のURLのパスへアプリケーションをマッピングしたり、HTTPS通信を実装したりすることもできます。
しかし、イングレスは標準クラスタのみでしか利用できません...残念っ
もし利用したかったら新しくフリークラスタではなく、標準クラスタを作成しましょう!

これ以外にもKubernetesには様々な機能があります。
私も今後もっともっと勉強して様々な技術をQiitaを通して紹介していこうと思います。

参考資料

・15Stepで習得 Dockerから入るKubernetes 高良真穂(著)

Dockerから入るKubernetes
私がもっともお世話になった技術書です。Kubernetesのことはもちろん、Dockerのことについても詳しく紹介されており、大変参考になります。今も隣にあります。私のこのQiita記事はこの本を参考に書いてありますので、もっともっと詳しく知りたい!という方は是非ともこの本を買って読んでください。個人開発から法人のシステム開発まで幅広く適用できる技術書です。また、IBM Cloud以外にも、GCPでのKubernetesの利用方法も書かれていますので、多くの方をターゲットにされた本だと思います。
amazonリンク (https://www.amazon.co.jp/dp/B08221TFBH/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)

・IBM Japan Channel(Youtube)


Youtubeチャンネルですね。これも大変お世話になっています。Kubernetesのハンズオンの紹介や、概念の説明をしたり、Kubernetesを小人の世界に例えて紹介されたりしています。是非ご覧ください。
URL(https://www.youtube.com/channel/UCR5bQwLkDls81oOkKbx45dw)

他にも色々参考にさせていただきましたが(例えばIBM公式の資料など)、最も参考にさせていただいた2点を紹介させていただきました。

終わりに

今回初めてQiitaの記事を書かせていただきました。
色々至らぬ点があると思います。
ここ間違ってるよ、とか、ここおかしいよ、とか。
是非ともご指摘いただければと存じます。
すぐに修正し、これからの励みにさせていただきます。
最後まで読んでいただきまして誠にありがとうございました。
良いお年を。

Viewing all 8943 articles
Browse latest View live