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

sequelize.jsを使ってみる。

$
0
0

sequelize.jsとは

node環境でDBを操作するための便利なライブラリです。
サーバーサイドをnode.jsで実装する際にexpress.jsと合わせて使用します。

CLIによるモデルの作成とDBマイグレーションについて説明した後、
ORMとしてのサーバーサイドでの呼び出し方法について記載します。

詳細はこちらをご参考ください。→https://sequelize.org/master/

導入方法

npm install --saveする。

npm install--save sequelize

モデルの作成とDBマイグレーション

モデル作成とDBへのマイグレーションはsequelize-cliを使用します。

インストール

npm install--save sequelize-cli

初期化処理

./node_modules/sequelize-cli/lib/sequelize init

コマンドを打つと、config、migrations、modelsといったディレクトリが作成されます。
config/config.jsonにDBへの接続設定を記入します。
migrationsはマイグレーションの管理をし、
modelsはモデルの管理をします。

モデルの作成

node_modules/.bin/sequelize model:create --name User --attributes userid:string,name:string,photo:text

DBへのマイグレーション

node_modules/.bin/sequelize db:migrate

2,呼び出し方と使い方

サーバーサイドでの呼び出し方と使い方について記載します。

呼び出し方

hoge.js
#オペレーションの導入(ver4.x~)const{Op}=require('sequelize');require('sequelize');#モデルの定義(cliで作成していた場合)上記cliで作成したModelを呼び出します。const{models:{Hoge,Fuga}}=require('cliで作成したモデルのPATH');#参考:モデルから読み込まれる部分(これがcliで作成したmodelの中に記載されています)sequelize.define('ModelName',{id:{type:DataTypes.INTEGER,primaryKey:true},name:DataTypes.STRING,},{timestamps:false,});

という感じです。

使い方

シンタックスには若干癖があります。
代表例としてSELECT文とUPDATE文を記載します。
返却されるのはPromiseオブジェクトです。
取得した後にthenのメソッドチェーンなどを使用することも可能です。

hoge.js
#SELECT(SELECTuseridasfuga,nameFROMHogeWHEREuserid=userid)Hoge.findAll({attributes:[['userid','fuga'],['name','name']],where:{[Op.or]:[{userid:{[Op.eq]:userid}}]},}));#UPDATE(UPDATEHogeSETuserid=userid,name=nameWHEREuserid=userid)Hoge.update({userid:userid,name:name},{where:{userid:userid}});

こんなエラーが出た

DeprecationWarning: String based operators are deprecated
という内容のエラーが出ることがあります。
要約すると、古いバージョンの書き方だから非推奨です。ということですが、
"sequelize": "^5.8.6"(4.44.3でエラーが出ました)のような感じでpackage.jsonを書き直すと直ります。

DeprecationWarning: String based operators are deprecated. Please use Symbol based operators for better security, read more at https://sequelize.org/master/manual/querying.html#operators

まとめ

クライアントをjavascriptフレームワーク使うなら、
サーバーサイドもjavascript(node.js)で書きたいということや、
大量のアクセスやリアルタイム処理に強いので、node.jsやexpress.jsを使用したいということもあるかと思います。
その際のDB周りの処理でsequelize.jsは便利です。
お役にたちましたら幸いです。


macOSのnpmでグローバルインストールに失敗する

$
0
0

事象

  1. -gオプション付きのnpmコマンドでパッケージをグローバルインストールしようとすると、次のエラーが発生する。

    % npm install-g typescript
    npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules
    npm ERR! code EACCES
    npm ERR! syscall access
    npm ERR! path /usr/local/lib/node_modules
    npm ERR! errno -13
    npm ERR! Error: EACCES: permission denied, access '/usr/local/lib/node_modules'
    npm ERR!  [Error: EACCES: permission denied, access '/usr/local/lib/node_modules']{
    npm ERR!   stack: "Error: EACCES: permission denied, access '/usr/local/lib/node_modules'",
    npm ERR!   errno: -13,
    npm ERR!   code: 'EACCES',
    npm ERR!   syscall: 'access',
    npm ERR!   path: '/usr/local/lib/node_modules'
    npm ERR! }
    npm ERR! 
    npm ERR! The operation was rejected by your operating system.
    npm ERR! It is likely you do not have the permissions to access this file as the current user
    npm ERR! 
    npm ERR! If you believe this might be a permissions issue, please double-check the
    npm ERR! permissions of the file and its containing directories, or try running
    npm ERR! the command again as root/Administrator.
    
    npm ERR! A complete log of this run can be found in:
    npm ERR!     /Users/foobar/.npm/_logs/2020-04-11T18_25_19_969Z-debug.log
    

原因

  1. NPMの公式サイトでアナウンスされています。
    https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally

  2. macOSのデフォルトでは、次のように設定されています。

    % npm config get prefix
    /usr/local
    

対処方法

  1. 次のコマンドを実行する。

    % mkdir ~/.npm-global
    % npm config set prefix '~/.npm-global'
  2. ~/.profileファイルを編集し、パスを通す。

    % vi ~/.profile
    
    export PATH=~/.npm-global/bin:$PATH
    
  3. ~/.profileファイルの内容をシェルに適用する。

    % source ~/.profile
    

動作確認

  1. エラーが発生しなくなることを確認する。

    % npm install-g typescript
    /Users/foobar/.npm-global/bin/tsserver -> /Users/foobar/.npm-global/lib/node_modules/typescript/bin/tsserver
    /Users/foobar/.npm-global/bin/tsc -> /Users/foobar/.npm-global/lib/node_modules/typescript/bin/tsc
    + typescript@3.8.3
    added 1 package from 1 contributor in 0.75s
    
    % tsc --version
    Version 3.8.3
    

JavaScriptでのimport/export方法

$
0
0

JavaScriptでは、exportやimportを使って関数やクラスを他のファイルから呼び出し/読み込みすることができますが、いろんな書き方があって何がどう違うのかごちゃごちゃしませんか?
少なくとも私はごちゃごちゃしちゃうので、備忘録も兼ねてまとめたいと思います。

es2015とNode.jsでの書き方の違い

まず最初の混乱ポイントは、
exportなのか exports(module.exports)なのか?
読み込みは importrequireどっちなの?

めちゃくちゃややこしいですよね。けどこれ実は別物で、
export/importは、ES2015(ES6)での書き方
exports/requireは、Node.js(CommonJS)での書き方なんです。

参考:CommonJSって何?という方は @naoki_mochizukiさんが書かれた JavaScriptが辿った変遷という記事を読んでみてください!JavaScriptの歴史がとてもわかりやすく説明されていて、CommonJSの正体もスッと理解できると思います!

以下に書き方を例を使ってまとめていきます。

export 方法一覧

ES2015(ES6)での書き方

1. 個々の機能をエクスポート

exportconstname="xxx";exportfunctionfuncName(){...}exportclassclassName{...}

2. 事前に宣言された機能をまとめてエクスポート

export{name,funcName,className};

3. デフォルトとして個別の機能をエクスポート

exportdefaultfunction(){}// orexportdefault{name,funcName,className}

Node.js(CommonJS)での書き方

4. 単一のモジュールをエクスポート

module.exports=xxx;

5. エクスポートしたい個別の機能を指定し、オブジェクトとしてエクスポート

module.exports={xxx,yyy};module.exports={xxxName:xxx,yyyName:yyy};

import 方法一覧

ES2015(ES6)での書き方

1. モジュールから特定のエクスポートをインポートする

import{xxx}from"./lib"import{xxx,yyy}from"./lib"

2. モジュールのコンテンツすべてをインポート

import*aslibfrom"./lib"

3. デフォルトをインポート

importxxxfrom"./lib"

Node.js(CommonJS)での書き方

4. エクスポートされたものをまとめてインポート

constxxx=require("./lib")

5. エクスポートされたものを個別にプロパティ名を指定してインポート

const{xxx,yyy}=require("./lib")

使用例

それでは、実際のコードの例を見て見ましょう。

ES2015(ES6)の場合

例1

export1.js
exportconstadd=(num1,num2)=>{returnnum1+num2;};exportconstminus=(num1,num2)=>{returnnum1-num2;};
import1.js
import{add,minus}from'./export1.js';add(1,2);// => 3minus(1,2);// => -1

個々の機能をエクスポートする場合、インポートしたい物を {} で括って個別に呼び出します。

例2

export2
constadd=(num1,num2)=>{returnnum1+num2;};constminus=(num1,num2)=>{returnnum1-num2;};export{add,minus};
import2.js
import{add,minus}from'./export2.js';add(1,2);// => 3minus(1,2);// => -1

事前に宣言された機能をまとめてエクスポートした場合も同じ。
インポートしたい物を {} で括って個別に呼び出します。

例3

export3
constadd=(num1,num2)=>{returnnum1+num2;};constminus=(num1,num2)=>{returnnum1-num2;};export{add,minus};
import3.js
import*ascalcfrom'./export3.js';console.log(calc);// => [Module] { add: [Function: add], minus: [Function: minus] }calc.add(1,2);// => 3calc.minus(1,2);// => -1

これは、calcという名前で export3.js のファイルのモジュールからのエクスポートすべてインポートします、という書き方です。

例4

export4
constadd=(num1,num2)=>{returnnum1+num2;};constminus=(num1,num2)=>{returnnum1-num2;};exportdefault{add,minus};
import4.js
importcalcfrom'./export4.js';console.log(calc);// => { add: [Function: add], minus: [Function: minus] }calc.add(1,2);// => 3calc.minus(1,2);// => -1

export defaultでエクスポートすると、インポート時に {} で括る必要がなく、まとめてインポートされます。

例5

export5.js
exportdefaultfunctionadd(num1,num2){returnnum1+num2;}
import5.js
importaddfrom'./export5.js';console.log(add(1,2));// => 3

export defaultする時には、個別の機能をダイレクトにエクスポートすると、インポートしたものをそのまま使用できます。

:warning:注意 :warning:
コマンドでこれらのファイルを実行する時に、下記のように nodeコマンドで実行しても、エラーが出て期待通りに実行できません。

$ node import5.js
/User/xxx/import5.js:1
import add from './export5.js';
^^^^^^

SyntaxError: Cannot use import statement outside a module
(エラーが続きます...)

なぜなら、ES2015(ES6) で書かれたコードは node コマンドでそのまま実行できないからです。

実行したい場合は、babel などのトランスパイラを使って ES2015(ES6) で書かれた JavaScript を Node.js が理解してくれるようにトランスパイルするなどの一手間を加える必要があります。

けど、今回はその環境を作るのがちょっとめんどくさかったので、下記オプションを付けて確認しました。
--experimental-modulesの説明は割愛します。)

$ node --experimental-modules import5.js 
(node:10746) ExperimentalWarning: The ESM module loader is experimental.
3

Node.js(CommonJS)の場合

例6

export6.js
constadd=(num1,num2)=>{returnnum1+num2;};constminus=(num1,num2)=>{returnnum1-num2;};module.exports=add;
import6.js
constadd=require('./export6');add(2,3);// => 5

import6.js でインポートする時に
const { add } = require('./export');という風に{} で括ってしまうと、
add(2, 3)実行時にエラーになってしまいます。

なぜでしょうか??

exportされているのは add
すなわち、関数自体が export されているのですが、addというプロパティ名でエクスポートされたものをインポートしようとしているので、そんなプロパティは存在しないよ、とエラーが発生してしまいます。

では次の例はどうなるでしょうか。

例7

export7.js
constadd=(num1,num2)=>{returnnum1+num2;};constminus=(num1,num2)=>{returnnum1-num2;};module.exports={add,minus};
import7-1.js
constadd=require('./export7');add.add(2,3)// => 5add.minus(2,3)// => -1add(2,3);// エラー

exports7.js では個々の関数をオブジェクトとしてエクスポートしているので、import7-1.jsのaddには、
{ add: [Function: add], minus: [Function: minus] }
というオブジェクトが代入されているのです。

ではもし、下記のように {} で括ってインポートしたらどうなるでしょうか。

import7-2.js
const{add,minus}=require('./export7');add(2,3)// => 5minus(2,3)// => -1

それぞれの変数には対応するプロパティの値、すなわち関数が代入されるので、そのまま関数として実行できるようになります。

最後に、これは少し極端な例を見てみましょう。

例8

export8.js
constadd=(num1,num2)=>{returnnum1+num2;};constminus=(num1,num2)=>{returnnum1-num2;};module.exports={addName:add,minusName:minus};
import8.js
const{addName,minusName}=require('./export8');addName(2,3)// => 5minusName(2,3)// => -1

もう何が起こっているかはわかりますね!
export8.js で、addという名前の関数を addNameというプロパティ名でエクスポートしているので、{ addName }でインポートしてあげる必要がありますね。

もしこれを const func = require('./export');とインポートしたら、
func.addName(2, 3)といった感じで呼び出してあげればOKです!

参考

Node Version Manager

$
0
0

Node.jsのバージョン管理ツール

  • node.jsはバージョン依存度が高いため、バージョン管理ツールが必須です。
  • 主要なものに「nodebrew」「nvm」などがあります。

どちらがおすすめ?

nvmのほうが人気です。
image.png


Cloud9でのNode.jsとnvm

既にプリインストールされています。

whitecat:~/environment $ node -v                                                                                                                                         
v10.19.0
whitecat:~/environment $ nvm --version
0.31.7
whitecat:~/environment $ 

nvmのバージョンアップ

whitecat:~/environment $ cd ~/.nvm/
whitecat:~/.nvm ((v0.31.7))$ git pull origin master
From https://github.com/creationix/nvm

whitecat:~/.nvm ((73438e3...))$ source ~/.nvm/nvm.sh
whitecat:~/.nvm ((73438e3...))$ nvm --version
0.35.3

nvmの設定状況

whitecat:~/environment $ nvm ls
->     v10.19.0
         system
default -> 10 (-> v10.19.0)
node -> stable (-> v10.19.0)(default)
stable -> 10.19 (-> v10.19.0)(default)

Node.jsの最新化

whitecat:~/environment $ nvm install stable --latest-npm
Downloading and installing node v13.12.0...
Now using node v13.12.0 (npm v6.14.4)
whitecat:~/environment $ nvm alias default stable
default -> stable (-> v13.12.0)
whitecat:~/environment $ nvm ls
       v10.19.0
->     v13.12.0
         system
default -> stable (-> v13.12.0)
node -> stable (-> v13.12.0)(default)
stable -> 13.12 (-> v13.12.0)(default)

npmのインストール

$
0
0

npmの2種類のインストール

  • ローカルインストール
    カレントディレクトリの node_modules 以下にパッケージをインストール。開発プロジェクト固有で依存するパッケージをインストールしたい時に使う。

  • グローバルインストール
    システム共通の場所にパッケージをインストール。ビルドツールなど、システム全体で利用するツール・コマンド類をインストールしたい時に使う。

ローカルインストール

  • --save
    package.jsonのdependencies に追記される。他の人が npm install した時に、dependencies に指定したパッケージが全てインストールされる。

  • --save-dev
    package.jsonのdevDependencies に追記される。npm install パッケージ名 --dev

  • --save-optional
    package.json の optionalDependencies に追記される。

違い

package.jsonがモジュールとして外部に公開し、他の人がnpm installした時に影響。

firebaseでexpress-generatorの起動方法

$
0
0

1

下記のURLで設定
https://qiita.com/yoshikoba/items/ff7beab24863db804f42

この通りに設定してもエラーが出ました。

2

package.jsonに

"engines":{"node":"8"},

を追加。

3

functionディレクトリ内で

npm install firebase-admin
npm install firebase-functions

4

firebase serveで localhost:5000を開けば
スクリーンショット 2020-04-12 16.41.38.png

表示されます。

バイナリベクタータイルをGeoJSONにデコードする2つの方法

$
0
0

Abstract

バイナリベクタータイルの実体は、XYZタイルの位置ごとにサーバーに設置されたPBFファイル群です。PBFファイルはプロトコルバッファと呼ばれる規格で(その詳細は把握していませんが)、GeoJSONをシリアライズしたバイナリデータです。つまり元はGeoJSONなので復元(デコード)可能という事です。今回は2つの手法でGeoJSONへのデコードを試みたいと思います。

tippecanoe-decode

mapbox社謹製のtippecanoeと言えばバイナリベクタータイルの生成で用いられる有名なライブラリですが、付随して、バイナリベクタータイルからGeoJSONへのデコード機能を持っています。

Install

brew install tippecanoe

Usage

tippecanoe-decode <target_pbf> <zoomlevel> <x> <y>

これが基本形で、特定のsource-layerのみを抽出する事も出来ます。

具体例
tippecanoe-decode sample.pbf 13 7121 3260 --layer=road

番外編

ベクタータイルをディレクトリで用意出来ている場合、ディレクトリを指定するだけでデコード可能です(この場合、minzoomとmaxzoomを指定してあげた方が安心な挙動をしそう)。しかし出力が少し使いにくいので参考まで(多分PBFファイルを個別に指定するよりこちらの方が速い)。

tippecanoe-decode bvtiles_dir -z 13 -Z 13 --layer=building

Node.js(vector-tile-js)

こちらもmapbox社製のnpmパッケージです。

Install

npm install @mapbox/vector-tile
npm install pbf
npm install fs

Usage

サンプルコードを示します。

sample.js
varPbf=require('pbf')varVectorTile=require('@mapbox/vector-tile')varfs=require('fs')letpbfdata=fs.readFileSync(PBFFILE_PATH)letpbf=newPbf(pbfdata)constlayer=newVectorTile.VectorTile(pbf).layers[SOURCE_LAYER_NAME];if(layer){for(leti=0;i<layer.length;i++){constfeature=layer.feature(i).toGeoJSON(X,Y,ZOOM_LEVEL);console.log(feature)//GeoJSON形式のFeature}}

References

Angular9でBootstrap4を使う

$
0
0

始めに

  • Angular 9系にBootstrap4 (ng-bootstrap) を適用する手順です。

  • 通常の Bootstrap(jQuery, popper.js依存)を使用してもよいですが、余計なライブラリに依存することになる事になるため、おすすめしません。

    • ng-bootstrapは、Bootstrapが依存しているjQuery, popper.jsの実装をAngularのcomponentに差し替えています。
  • メジャーバージョンはしっかりと確認する。

    • 特に Angular5 と Angular6+では CLI の設定ファイル周りが大きく変更となっています。
  • 公式の英語ドキュメント読むのめんどいって方向け。

環境

タイトルにもある通り、今回はAngular9系にBootstrap4を適用します。

$ ng --version 
     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 9.0.7
Node: 12.14.1
OS: linux x64

Angular: 9.0.7
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Ivy Workspace: Yes

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.900.7
@angular-devkit/build-angular     0.900.7
@angular-devkit/build-optimizer   0.900.7
@angular-devkit/build-webpack     0.900.7
@angular-devkit/core              9.0.7
@angular-devkit/schematics        9.0.7
@angular/localize                 9.1.1
@ngtools/webpack                  9.0.7
@schematics/angular               9.0.7
@schematics/update                0.900.7
rxjs                              6.5.5
typescript                        3.7.5
webpack                           4.41.2

手順

ng-bootstrapをインストールする

Angular 9.0.0 以上かつ ng-bootstrap 6.0.0 以上の場合は@angular/localizeをpolyfillsに追加する必要があります。

$ ng add @angular/localize

ng-bootstrapをインストールする。(Bootstrapに依存しているため、一緒にインストールします。)

$ npm install @ng-bootstrap/ng-bootstrap bootstrap

ルートモジュールにインポートする。

app.module.ts
import{BrowserModule}from'@angular/platform-browser';import{NgModule}from'@angular/core';import{AppRoutingModule}from'./app-routing.module';import{AppComponent}from'./app.component';import{NgbModule}from'@ng-bootstrap/ng-bootstrap';@NgModule({declarations:[AppComponent],imports:[BrowserModule,AppRoutingModule,NgbModule,],providers:[],bootstrap:[AppComponent]})exportclassAppModule{}

angular.jsonに設定を追加する

グローバルスタイルとして、bootstrap を使用するために以下を追記する。

angular.json
"styles":["./node_modules/bootstrap/dist/css/bootstrap.min.css","src/styles.css"]

動作確認

app.component.html
<buttontype="button"class="btn btn-primary">test</button>

アプリケーションを起動後、http://localhost:4200にアクセスし、以下の画面が見えればOKです。

image-20200412180154202.png

参考


【NodeJS】AWS Lambda+APIGateway+DynamoDBでTODOアプリを作成

$
0
0

はじめに

サーバーレスを趣味で始めてみたので、勉強がてらAWS Lambda+APIGateway+DynamoDBでTODOアプリを作成し始めました。
個人のメモとして、サーバー側とクライアント側での処理を記事にまとめます。(まだ作成途中です)

概要

Lambdaで使用する言語はNode
DynamoDBはidとtodoという項目のみを持ちます。
テーブル名は「todoTable」
今回は、GETとPOSTのみを実装します。

Lambdaで関数作成

本来であれば1つの関数にまとめたかったのですが、GETメソッドであることをLambdaに渡す方法がわからず、ひとまず関数を分けました。

まずはGETメソッドから。
単純に全てのカラムを取得します。(本来はあまりscanは使わない方がいいらしいが)

constAWS=require('aws-sdk')constdynamo=newAWS.DynamoDB.DocumentClient()exports.handler=(event,context,callback)=>{consthttpMethod=event.httpMethodconstparams={'TableName':'todoTable'}dynamo.scan(params,function(err,data){constresponse={statusCode:200,body:JSON.stringify(data.Items)}callback(null,response)})};

クライアント側でbodyからtodoListを取り出し表示するようにします。

続いてPOSTメソッド。

constAWS=require('aws-sdk')constdynamo=newAWS.DynamoDB.DocumentClient()exports.handler=(event,context,callback)=>{constparams={'TableName':'todoTable','Item':{'id':event.id,'todo':event.todo}}dynamo.put(params,function(err,data){if(err){console.log('error')}else{console.log('success')constresponse={statusCode:200,body:JSON.stringify(data)}context.done(null,response)}})};

try-catchでやるべきですが、今回は簡略化のためこのような実装にしました。(あとで全てtyr-catchに直します)
なぜかここでcallbackを使うと怒られました。
理由分かる方、教えていただきたいです。

あとはAPIGatewayでこれらの関数を各メソッドに割り当てて、デプロイしてください。
詳細は省きます。

クライアントサイド

今回はjQueryは使わずにXMLHttpRequestを使用して実装します。

まずは、画面がロードされた際にGETメソッドでテーブルの項目を取得し、表示をします。

window.onload=()=>{constrequest=newXMLHttpRequest()request.open('GET','https://ほにゃらら.amazonaws.com/dev',true)request.responseType='json'request.onload=event=>{constdata=this.responseconsttodoList=JSON.parse(data.body)for(taskoftodoList){constitem=task.todo// HTMLのulにliを突っ込むだけのメソッドcreateTodoList(item)}}request.send()}

特に難しい部分はなし。

続いて、TODOを追加した際に走るイベントを追加。

// addというinputタグにイベントを追加constaddTask=document.querySelector('.add')addTask.addEventListener('submit',e=>{e.preventDefault()constid=Math.random().toString(36).slice(-8);consttask=addTask.add.value.trim()constitem={'id':id,'todo':task}constrequest=newXMLHttpRequest()request.open('POST','https://ほにゃらら.amazonaws.com/dev/insert',true)request.setRequestHeader('Content-Type','application/json');request.send(JSON.stringify(item))if(task.length){createTodoList(task)addTask.reset()}})

これで完成です。サーバーレスは短時間で割といろいろできて楽しいですね!

躓いたところ

CORS問題

Access-Control-Allow-Origin 

これを理由にずっと怒られていました。
↓参考記事
【解説付き】chromeでXMLHttpRequestをローカルのファイルで行う方法
AWS API Gateway クロスドメイン通信

XMLHttpRequestのパラメータの渡し方

現在のコード

request.send(JSON.stringify(item))

過去のコード(エラー発生)

request.send(item)

JSON形式にしておらず、30分くらい悩まされていました。。。

終わり

今後はUPDATE、DELETEの実装をし、全て1つの関数にまとめたいと思っています。
それが終わったらなんかしらアプリ作ってみようかな。

http-serverによるローカル HTTPS server 構築 (5分でできる)

$
0
0

はじめに

https環境でないと動かないJavascript APIなどを試験するのにローカルになんちゃってhttpsサーバが欲しくなった。以下が簡単で最速かなと思ったのでここに記す。

mkcertのインストール

Macの場合

% brew install mkcert

CAのインストール(初回のみ)

% mkcert -install

サーバ証明書、秘密鍵生成

下記コマンドで適当なフォルダに証明書、秘密鍵を生成。mkcertコマンドには証明したいドメインを全て指定。 ローカルネットワークの他PCからもアクセスしたいので、自PCのPrivate Address (e.g. 192.168.11.2)も追加(各自適当に)。

% mkdir ~/cert
% cd cert
% mkcert localhost 127.0.0.1 192.168.11.2
% ls
localhost+2-key.pem localhost+2.pem

http-serverインストール

% npm install -g http-server

サーバ起動

サーバ証明書、秘密鍵を指定してhttp-serverを起動する.

% http-server -c-1.--ssl--key ~/cert/localhost+2-key.pem --cert ~/cert/localhost+2.pem

ブラウザから https://localhost/, https://192.168.11.2/などにアクセス.

特定のジオメトリが含まれるすべてのXYZタイルを取得する

$
0
0

Abstract 要旨

GISのデータはラスター(画像)とベクター(点・線・ポリゴン)の2つに大別されます。これらはしばしば膨大なサイズとなり、効率的なデータ配信のためにXYZタイルという概念が存在します。XYZタイルの概念の詳細な説明は割愛しますが、でかいデータを分割して必要な箇所だけ配信する仕組みです。標準規格なのでフレームワークやアプリケーションを問わず、極地の一部を除く地球全域が常に同じタイルに区分されます。
「この地物ってXYZタイルだとどこに属するんだろう」「日本列島を網羅するXYZタイルの一覧が欲しい」というニーズが、ここ最近の私の中でありましたので、Node.jsモジュールとPythonモジュールのそれぞれのパターンについて本記事で説明します。
なおXYZタイルは、平面に落とした地球を一定のルールで分割しているため、パラメータをもとに計算する事も可能です(参考:Qiita - 地図タイルの計算まとめ)。

Sample Data 題材

北海道のポリゴンをディゾルブしたGeoJSONを題材とします。
国土地理院 - タイル座標確認ページによれば、北海道が含まれるタイルは以下の画像のとおり(例としてズームレベル7)。検証のために、目視してタイルを一覧にしておきます。
スクリーンショット 2020-04-10 21.57.24.png

ズームレベル7のときに北海道が含まれるすべてのタイル
[x, y, z]
[114, 45, 7]
[116, 45, 7]
[113, 46, 7]
[114, 46, 7]
[115, 46, 7]
[116, 46, 7]
[113, 47, 7]
[114, 47, 7]
[115, 47, 7]

Envs 実行環境

  • macOS 10.15.4
  • Node.js v12.16.1
  • Python 3.8.2

JavaScript Node.js module / tile-cover

GitHub - Mapbox / tile-cover
License:MIT

Install インストール

npm install @mapbox/tile-cover

Usage 使い方

ローカルのGeoJSONファイルを読み込んで、その地物が含まれるすべてのタイルのインデックスを取得するサンプルです。前述のとおり、北海道のポリゴンをディゾルブによりひとつの地物として扱っています(tile-coverは緯度経度で計算するのでCRSはEPSG:4326です)。

tileindex.js
//Library Importvarfs=require('fs')varcover=require('@mapbox/tile-cover');//Load local GeoJSON filevargeojson_str=fs.readFileSync('./dissolved_hokkaido.geojson')vargeojson_obj=JSON.parse(geojson_str);//use tile-covervarlimits={min_zoom:7,max_zoom:7};vartile_index=[]tile_index=cover.tiles(geojson_obj.features[0].geometry,limits)console.log(tile_index)

本スクリプトをコンソールで実行、タイル一覧が出力される。

node tileindex.js
[[ 114, 45, 7 ],
  [ 116, 45, 7 ],
  [ 113, 46, 7 ],
  [ 114, 46, 7 ],
  [ 115, 46, 7 ],
  [ 116, 46, 7 ],
  [ 113, 47, 7 ],
  [ 114, 47, 7 ],
  [ 115, 47, 7 ]]

ちゃんと9枚すべてがリストアップされました。

Tips 参考

  • cover.tiles()の引数は、GeoJSONのgeometryのObjectとmin_zoomとmax_zoomによるObject
  • min_zoomとmax_zoomには同じ値を入れる事を推奨(少し挙動にクセがある)

Python module / tiletanic

GitHub - DigitalGlobe / tiletanic
License:MIT

Install インストール

pip install tiletanic

Usage 使い方

Node.js編と同様、ディゾルブされた北海道を題材としますが、tiletanicでは緯度経度ではなくWebメルカトルの座標を用いるため、CRSをEPSG:3857としておきます(内部的にCRSを変換する事も可能ですが手間がかかるので、データ自体をあらかじめQGISで変換しました)。

tileindex.py
importjsonimportshapelyimporttiletanicif__name__=="__main__":zoomlevel=7#geojson読み込み
geojson_dict={}withopen('./dissolved_hokkaido_3857.geojson','r')asf:geojson_dict=json.load(f)#ディゾルブにより地物数は1
feature=geojson_dict['features'][0]['geometry']feature_shape=shapely.geometry.shape(feature)#タイルスキームの初期化
tiler=tiletanic.tileschemes.WebMercator()covering_tiles_itr=tiletanic.tilecover.cover_geometry(tiler,feature_shape,zoomlevel)covering_tiles=[]fortileincovering_tiles_itr:tile_xyz=[tile[0],tile[1],tile[2]]covering_tiles.append(tile_xyz)print(covering_tiles)

Pythonで実行

python tileindex.py
[[114, 45, 7], [113, 46, 7], [113, 47, 7], [114, 46, 7], [115, 46, 7], [114, 47, 7], [115, 47, 7], [116, 45, 7], [116, 46, 7]]

Node.jsのtile-coverと出力される順番は異なりますが、ちゃんと9枚のタイルのインデックスが出力されました。

Tips 参考

  • tiletanic.cover_geometryの引数は、tiletanic.tileschemesのインスタンス、shapely.geometryのインスタンス、ズームレベル値です
  • tiletanicをpipでインストールする際、依存先であるshapelyも同時にインストールされます

Sample codes サンプルコード

GitHub - Kanahiro / tileindex-sample
GeoJSONファイルを含めたすべてのコードが上記のリポジトリにあります。

DynamoDBで"The provided starting key does not match the range key predicate"とエラーが出た場合の対策(node.js)

$
0
0

Lambda(node.js)で、DynamoDBから超膨大なレコードを取得するプログラムを開発していました。
1分単位でクエリを実行して、レコードを小分け取得するようにしたのですが、
その際にプロパティを初期化できていなかったため、標題のエラーが発生したようです。

っていうことが、以下の記事を見てわかりました

https://qiita.com/nanananamememe/items/52293f1fece4458cc2fa

具体的な対策は delete params["ExclusiveStartKey"];みたいな感じで
プロパティを消せば良いです。

すっごく長いですが、備忘録を兼ねてソースコードを掲載します

try{varENV=require('dotenv').config();varAWS=require('aws-sdk');AWS.config.update({region:process.env['AWS_DYNAMO_DB_REGION']});vardocClient=newAWS.DynamoDB.DocumentClient({apiVersion:'2012-08-10'});}catch(ex){console.error(ex);}varglobalSecondaryIndexes=[{key:'dummy',index:'dummy-created_at-index',}];varprimaryKeys={partitionKey:'log_id',sortKey:'created_at'};asyncfunctiongetBulk(runTime=null){varmyRunTime=runTime?runTime:newDate();varmyQuery="#partitionKey = :partitionVal AND #rangeKey BETWEEN :rangeStart and :rangeEnd";varparams={TableName:process.env['AWS_DYNAMO_DB_ACCESS_LOG_TABLE_NAME'],IndexName:globalSecondaryIndexes[0].index,ExpressionAttributeNames:{"#partitionKey":globalSecondaryIndexes[0].key,"#rangeKey":primaryKeys.sortKey},ExpressionAttributeValues:{":partitionVal":1,":rangeStart":0,":rangeEnd":0},KeyConditionExpression:myQuery};varresults=newObject();vardateRanges=[{"start":1586762880,"end":1586762939,},{"start":1586762940,"end":1586762999,}];try{for(variindateRanges){vardateRange=dateRanges[i];params["ExpressionAttributeValues"][":rangeStart"]=dateRange["start"];params["ExpressionAttributeValues"][":rangeEnd"]=dateRange["end"];deleteparams["ExclusiveStartKey"];// <-- ★★★★★★ これ!! ★★★★★★varresult=awaitget(params);// Array.objectresults[dateRange["start"]]=result;}returnresults;// Object.Array.object}catch(ex){console.error("Whoops, looks like something went wrong.");console.error(ex);thrownewError(ex);}};asyncfunctionget(params){try{// 初回のクエリ発行varresponse=awaitqueryGet(params);varresults=response.data;// Array.object// 二回目以降のクエリ発行while(response.LastEvaluatedKey){params.ExclusiveStartKey=response.LastEvaluatedKey;response=awaitqueryGet(params);// オブジェクトをマージArray.prototype.push.apply(results,response.data);}// オブジェクトを返すreturnresults;// Array.object}catch(ex){myFunc.logError("Whoops, looks like something went wrong.");myFunc.logError(ex);thrownewError(ex);}};functionqueryGet(parameter){returnnewPromise((resolve,reject)=>{docClient.query(parameter,(err,data)=>{if(err){console.error(err);reject(err);}else{if(data.LastEvaluatedKey){// 全件取得できていない場合、最後の取得位置を通知して再帰処理を行うresolve({data:data.Items,count:data.Count,LastEvaluatedKey:data.LastEvaluatedKey});}else{// 初回または最後の取得処理時resolve({data:data.Items,count:data.Count});}}});});};

LoopBackのTodoListチュートリアルを実施

$
0
0

概要

Node.jsのフレームワークであるLoopBackのTodoListを作成するチュートリアルを実施する。
参考:https://loopback.io/doc/en/lb4/Getting-started.html#create-a-new-project

上記のチュートリアルでは、DBにIn-MemoryDBを利用しているが、ここではMySQLを利用する。

環境構築

以下を利用してDockerで環境を構築する。
https://github.com/Esfahan/docker-loopback-tutorial

TODO Tutorial

todo-list tutorialを行う。
参考:https://loopback.io/doc/en/lb4/todo-tutorial.html

Requirements

  • Node.js at v10 or greater

アプリケーションを作成

Scaffoldをする。
参考:http://loopback.io/doc/en/lb4/todo-tutorial-scaffolding.html

[root@882e7183f062 code]#lb4 app
? Project name: todo-list
? Project description: A todo list API made with LoopBack 4.
? Project root directory: todo-list
? Application class name: TodoListApplication
? Select features to enable in the project Enable eslint, Enable prettier, Enable mocha, Enable loopbackBuild, Enable vscode, Enable docker, Enable repositories, Enable services

#omitted

added 623 packages from 807 contributors and audited 5182 packages in 14.122s
found 0 vulnerabilities


Application todo-list was created in todo-list.

Next steps:

$cd todo-list
$npm start

Mysql用のDataSourceを作成

チュートリアルではIn-MemoryDBを利用するが、ここではMySQLを利用する。

MySQL用のパッケージをインストール

$npm install loopback-connector-mysql --save

MySQL用のDataSourceを作成

$lb4 datasource
? Datasource name: db
#方向キーでスクロールしてMySQLを選択
? Select the connector for db: MySQL (supported by StrongLoop)
#そのままEnter
? Connection String url to override other settings (eg: mysql://user:pass@host/db):
? host: loopback-db
? port: 3306
? user: loopback
? password: [hidden]
? database: loopback
   create src/datasources/db.datasource.config.json
   create src/datasources/db.datasource.ts
   update src/datasources/index.ts

Datasource Db was created in src/datasources/

Mysqlの接続情報は以下に作成される。
src/datasources/db.datasource.config.json

src/datasources/db.datasource.config.json
{"name":"db","connector":"mysql","url":"","host":"loopback-db","port":3306,"user":"loopback","password":"xxxxxxxxxx","database":"loopback"}

Modelを作成

参考:http://loopback.io/doc/en/lb4/todo-tutorial-model.html

lb4 model
? Model class name: todo
#Entityを選択
? Please select the model base class Entity (A persisted model with an ID)
? Allow additional (free-form) properties? No
Model Todo will be created in src/models/todo.model.ts

Let's add a property to Todo
Enter an empty property name when done

? Enter the property name: id
? Property type: number
? Is id the ID property? Yes
? Is id generated automatically? Yes

#? Is id generated automatically? No
#? Is it required?: Yes
#そのままEnter
? Default value [leave blank for none]:

Let's add another property to Todo
Enter an empty property name when done

? Enter the property name: title
? Property type: string
? Is it required?: Yes
#そのままEnter
? Default value [leave blank for none]:

Let's add another property to Todo
Enter an empty property name when done

? Enter the property name: desc
? Property type: string
? Is it required?: No
#そのままEnter
? Default value [leave blank for none]:

Let's add another property to Todo
Enter an empty property name when done

? Enter the property name: isComplete
? Property type: boolean
? Is it required?: No
#そのままEnter
? Default value [leave blank for none]:

Let's add another property to Todo
Enter an empty property name when done

#そのままEnter(空でEnterで終了する)? Enter the property name:
   create src/models/todo.model.ts
   update src/models/index.ts

Model Todo was created in src/models/

Migrationを実行してDBにTableを作成

Migrationを実行する。Modelファイルの中のTable定義を参照してTableが生成される。
今回の場合は、src/models/todo.model.tsの以下の部分が参照される。

src/models/todo.model.ts
exportclassTodoextendsEntity{@property({type:'number',id:true,generated:true,})id?:number;@property({type:'string',required:true,})title:string;@property({type:'string',})desc?:string;@property({type:'boolean',})isComplete?:boolean;

Migrationを実行

$ npm run-script build
$ npm run migrate

既存のテーブルを全て削除してmigrationする場合は以下。

$ npm run migrate -- --rebuild

作成されるTable

mysql>showcreatetableloopback.Todo;+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+|Table|CreateTable|+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+|Todo|CREATETABLE`Todo`(`id`int(11)NOTNULLAUTO_INCREMENT,`title`varchar(512)NOTNULL,`desc`varchar(512)DEFAULTNULL,`isComplete`tinyint(1)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8|+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+1rowinset(0.00sec)

Repositoryを作成

参考:https://loopback.io/doc/en/lb4/todo-tutorial-repository.html

$lb4 repository
#そのままEnter
? Please select the datasource DbDatasource

#スペースを押して選択する
? Select the model(s) you want to generate a repository
❯◉ Todo

#そのままEnter
? Please select the repository base class DefaultCrudRepository (Legacy juggler bridge)
   create src/repositories/todo.repository.ts
   update src/repositories/index.ts

Repository TodoRepository was created in src/repositories/

Controllerを作成

参考:https://loopback.io/doc/en/lb4/todo-tutorial-controller.html

lb4 controller
? Controller class name: todo
Controller Todo will be created in src/controllers/todo.controller.ts

#2つ目を選択
? What kind of controller would you like to generate?
  Empty Controller
❯ REST Controller with CRUD functions
#Todoを選択
? What is the name of the model to use with this CRUD repository? Todo
#TodoRepositoryを選択
? What is the name of your CRUD repository? TodoRepository
? What is the name of ID property? id
? What is the type of your ID? number
? Is the id omitted when creating a new instance? Yes
#そのままEnter
? What is the base HTTP path name of the CRUD operations? /todos
    force src/controllers/todo.controller.ts
   update src/controllers/index.ts

Controller Todo was created in src/controllers/

ブラウザからアクセスして動作を確認する

参考:https://loopback.io/doc/en/lb4/todo-tutorial-putting-it-together.html

loopbackを起動

$npm start
>todo-list@1.0.0 prestart /code/todo-list
>npm run build
>todo-list@1.0.0 build /code/todo-list
>lb-tsc
>todo-list@1.0.0 start /code/todo-list
>node -r source-map-support/register .
Server is running at http://[::1]:3000
Try http://[::1]:3000/ping

ブラウザからアクセス

http://localhost:3000

Screen Shot 2020-04-13 at 17.06.16.png

API経由でデータを追加する

$curl -H'Content-Type:application/json'\-d'{ "title": "get the milk" }'\
  http://localhost:3000/todos
{"id":1,"title":"get the milk"}

$curl -H'Content-Type:application/json'\-d'{ "title": "forget the milk " }'\
  http://localhost:3000/todos
{"id":2,"title":"forget the milk "}

DBを確認

mysql>select*fromloopback.Todo;+----+------------------+------+------------+|id|title|desc|isComplete|+----+------------------+------+------------+|1|getthemilk|NULL|NULL||2|forgetthemilk|NULL|NULL|+----+------------------+------+------------+2rowsinset(0.00sec)

Todoリストを確認

http://localhost:3000/todos

Response
[{"id":1,"title":"get the milk","desc":null,"isComplete":null},{"id":2,"title":"forget the milk ","desc":null,"isComplete":null}]

データを更新

$ curl --request PATCH -H 'Content-Type:application/json' \
  -d '{ "desc": "need milk for cereal." }' \
  http://localhost:3000/todos/1

Todoリストを確認

http://localhost:3000/todos

id:1のdescがUpdateされた。

Response
[{"id":1,"title":"get the milk","desc":"need milk for cereal.","isComplete":null},{"id":2,"title":"forget the milk ","desc":null,"isComplete":null}]

データを削除

$ curl --request DELETE http://localhost:3000/todos/1

Todoリストを確認

http://localhost:3000/todos

id:1が削除された。

Response
[{"id":2,"title":"forget the milk ","desc":null,"isComplete":null}]

以上

discord.js + Klasa を使ったBotの作成覚え書き

$
0
0

参考 : discord.jsのフレームワークKlasaが強すぎる
合わせて読んでください。

準備

  • 環境
    • Win10
    • Node.js 12.13.0
    • npm 6.12.0

適当にプロジェクトフォルダを作成し discord.js と Klasa をインストール

npm install--save discordjs/discord.js dirigeants/klasa

コーディング

最初の一歩

app.jsを作成

app.js
constsecret=require('./secret.js');// BOTトークンはここに記述consttoken=secret.token;const{Client}=require('klasa');newClient({prefix:'!!',// ボットのPrefixlanguage:'ja-JP',// 言語コード}).login(token);

この状態で一度 node app.jsから起動し、危険なログが出てなければとりあず成功。

コマンドの作成

commands/general配下に以下のソースを作成。Klasaはファイル名がそのままコマンド名になる。
ディレクトリ名はヘルプのカテゴリ名になる。
なお、下のソースに書かれている usageは単なる説明文ではなく引数のマッチャを参照するための記述なので注意。(これで30分ぐらい潰した……)

commands/general/hello.js
const{Command}=require('klasa');module.exports=classextendsCommand{/**
   * @constructor
   * @param  {...any} args
   */constructor(...args){// コマンドのオプション: https://klasa.js.org/#/docs/klasa/master/typedef/CommandOptionssuper(...args,{description:'挨拶を返す',usage:'<name:string>',// <>は必須引数、[]はオプション引数、<名前:型>のように書く});}/**
   * @param {*} message
   */asyncrun(message,[name]){returnmessage.sendMessage(`${name}さんこんにちは!`);}};

参考 :
Klasa : CreatingArguments
Klasa : CreatingCommands

BOTの反応はこんなかんじ

コメント 2020-04-14 002903.png

コメント 2020-04-14 002903_.png

データストアの作成

providersディレクトリにプロバイダを作成する。公式を見るとなんか適当に書いてよさそうだったので適当に書いてみる。

providers/datas.js
const{Provider}=require('klasa');module.exports=classextendsProvider{/**
   * @constructor
   * @param  {...any} args
   */constructor(...args){super(...args,{name:'datas'});this.counter=0;}/**
   * init
   */init(){this.counter=1;}/**
   * countup
   * @return {Number} count
   */countup(){this.counter++;returnthis.counter;}};

hello.jsから動作確認してみる。

const{Command}=require('klasa');module.exports=classextendsCommand{/**
   * @constructor
   * @param  {...any} args
   */constructor(...args){// コマンドのオプション: https://klasa.js.org/#/docs/klasa/master/typedef/CommandOptionssuper(...args,{description:'挨拶を返す',usage:'<name:string>',});this.data=this.client.providers.get('datas');}/**
   * @param {*} message
   */asyncrun(message,[name]){returnmessage.sendMessage(`${name}さんこんにちは!${this.data.countup()}回めの挨拶ですね!`);}};

コメント 2020-04-14 005829.png

本当に適当に動いてしまった。

npmでローカルのパッケージをinstallする方法

$
0
0

この投稿ではnpmコマンドで、ローカルのパッケージをインストールする方法を紹介します。

通常npm intallはnpmjs.comで公開されたパッケージをダウンロードしてきてインストールしますが、ここで説明するのはローカルにのみ存在する自作のライブラリをinstallする方法です。

npmでローカルのパッケージをinstallする方法

ローカルのパッケージをインストールには、単純にインストールしたいパッケージのファイルパスをnpm intallの引数にします:

npm install インストールしたいパッケージのパス

ローカルパッケージをインストールする具体例

以下の図のように、myappパッケージとmylibパッケージがあり、myappからmylibを使いたい例を見てみましょう。

mylibindex.jsでは、helloWorld変数が提供されていて、myappmain.jsでそれを使いたいとします。

このとき、myappパッケージにてnpm install ../mylibを実行するとこれが実現します:

$ npm install ../mylib

+ mylib@1.0.0
added 1 package and audited 1 package in 0.74s
found 0 vulnerabilities

インストールされると、package.jsonmylibへの依存設定が追加されます:

package.json
{"name":"myapp","version":"1.0.0","license":"MIT","dependencies":{"mylib":"file:../mylib"}}

main.jsを実行してみます:

$ node main.js
Hello World!

しっかりとmylibの変数が使われているが分かります。

ちなみに、インストールされたmyapp/node_modules/mylib../../mylibへのシンボリックリンクになります:

$ ls -la myapp/node_modules
lrwxr-xr-x 11 suin 14 Apr  9:35 mylib -> ../../mylib

Yarnでローカルのパッケージをaddする方法

$
0
0

この投稿ではyarnコマンドで、ローカルのパッケージをインストールする方法を紹介します。

通常yarn addはnpmjs.comで公開されたパッケージをダウンロードしてきてインストールしますが、ここで説明するのはローカルにのみ存在する自作のライブラリをaddする方法です。

(npmで同様のことをする方法は、「npmでローカルのパッケージをinstallする方法」をご覧ください)

やりかた

ローカルのパッケージをインストールには、単純にインストールしたいパッケージのファイルパスをyarn addの引数にします:

yarn add インストールしたいパッケージのパス

node_modulesにインストールされるパッケージをシンボリックリンクにしたい場合:

yarn add link:インストールしたいパッケージのパス

ローカルパッケージをインストールする具体例

少し具体例をもとに説明します。

以下の図のように、myappパッケージとmylibパッケージがあり、myappからmylibを使いたい例を見てみましょう。

mylibindex.jsでは、helloWorld変数が提供されていて、myappmain.jsでそれを使いたいとします。

このとき、myappパッケージにてyarn add ../mylibを実行するとこれが実現します:

$ yarn add ../mylib

yarn add v1.22.4
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ mylib@1.0.0
info All dependencies
└─ mylib@1.0.0
✨  Done in 0.08s.

インストールされると、package.jsonmylibへの依存設定が追加されます:

package.json
{"name":"myapp","version":"1.0.0","license":"MIT","dependencies":{"mylib":"../mylib"}}

main.jsを実行してみます:

$ node main.js
Hello World!

しっかりとmylibの変数が使われているが分かります。

ちなみに、インストールされたmyapp/node_modules/mylibmylibをごっそりコピーしたものになります。../../mylibへのシンボリックリンクではないので注意です。

シンボリックリンクにしたい場合はファイルパスの頭にlink:を付けます:

yarn add link:../mylib

vue-good-tableをTypeScriptで使おうとして TS7016: Could not find a declaration file for module 'vue-good-table'.

$
0
0

使用バージョン

$ npm list vue typescript vue-good-table
app@1.0.0
├── typescript@3.8.3
├── vue@2.6.11
└── vue-good-table@2.19.1

問題

Vue.jsをTypeScriptで使っていて、以下ドキュメントどおりに進めたところ、コンパイルエラー発生。

https://xaksis.github.io/vue-good-table/guide/#installation

エラー内容

TS7016: Could not find a declaration file for module 'vue-good-table'.'[app]/node_modules/vue-good-table/dist/vue-good-table.cjs.js'
implicitly has an 'any' type.

解決

[app]/@types/以下にファイル作成。

vue-good-table.d.ts
declaremodule'vue-good-table'

TypeScriptまだ良く分からないですが、moduleを宣言すれば良いらしい。
@types/に関しては、d.tsファイルをそこに置くとコンパイル時に読み込んでくれるよう。

※ tsconfig.json の typeRoots のデフォルト設定が @types ディレクトリになっています。
https://qiita.com/Nossa/items/726cc3e67527e896ed1e

VueGoodTableはanyだけど、とりあえずコンパイルはできました!

Alfred4 + emoj|Alfred4で簡単に絵文字が入力できるらしい!→alfred-emojiに変更

$
0
0

Alfred4で簡単に絵文字が入力できるらしい!

しかも無駄にディープラーニングやってるとか(alfred-emoj)

 ↓

違うワークフロー(alfred-emoji)に変更
こっちのほうが新しい、インストール簡単、使い方もわかりやすい

やったこと

  • Alfred4インストール、PowerPack登録
  • Node.jsのインストール
  • npm -g install xxxに失敗するので、npmのデフォルトディレクトリを作成する
  • alfred-emojのインストール
  • Alfred Preferences > Workflows > Emoj > Copy to Clipboard > [x]Automatically paste to front most app

これはおもしろい!😆

 ↓

- aflred-emojiのインストールに変更

参考リンク

Alfred3 おすすめワークフロー 7選 + 1
https://qiita.com/ryo2132/items/ef392999d32732fa0455

alfred-emoj
https://github.com/sindresorhus/alfred-emoj?ref=producthunt

Node.js®
https://nodejs.org/en/

【備忘録】npm -g install に失敗する
https://qiita.com/NaokiIshimura/items/cc07441939b226e779c6

 ↓

【Mac向け】よく使うAlfred Workflowを晒してみます【絵文字入力、FontAwesomeアイコン検索、エンコード/デコード、数値の基数変換】
https://blog.ttskch.com/mac-alfred-workflow-introducing/

alfred-emoji
https://github.com/jsumners/alfred-emoji

[LINE bot] NowとHerokuで常時起動化させる

$
0
0

この記事の内容

  • LINEボットの完成形、常時稼働化のやり方
  • そもそもモヤついていたところを自分なりにまとめてみた

完成品

(いじってたらなぜかアイコンが黒く怪しくなってしまった・・・どうやって戻すんだろう)
image.png

猿よりは賢いアプリを作った。(ただし、人間ほど完璧な回答はできない。)

  • ?を付けて聞くと文章で返します
  • ?がない時はwikipediaのリンクを返します
  • お猿さんが知らない単語のときは、、無視します。。

QRコード(お友達になってあげてね!)

※このアプリはこの後紹介するHerokuを使ってます。
smartmonky.png

作っていて思ったこと・・・

そもそもexpress,ngrok,now,herokuってそれぞれなんだろう。普通のサーバーと何が違うのだろう。

この辺りがモヤつくポイントだと思うので、自分なりの理解でまとめます。

普通のサーバー

以前、レンタルサーバーにphpファイルを置いて、そこでwebhookを受けて処理をするように作り替えた。
常に動くLINEBOTにお引っ越し(レンタルサーバ+PHP編)

こんなイメージ。
レンタルサーバ.png

Express

Node.jsで利用できるwebアプリケーションフレームワーク。

constexpress=require('express');constapp=express();

requireでExpressを利用可能にし、expressのアプリケーションオブジェクトをappに格納する。
そうすることで、app.post()postリクエスト時の処理や、app.listen()サーバー起動などの処理が簡単に記述できる。

Expressでローカル環境にアプリケーションを構築することができるが、ローカル環境は基本的に外部に公開しないのでLINEbotからの外部とのやりとりができない。
express.png

ngrok

Expressを用いて作った手元のlocalhostにグローバルからアクセスできるようにするトンネリングツール。
設定等はこちらを参照。
ngrokのサブドメインを取得して、その/webhookに対してアクセスすることでngrokを通して、

app.post('/webhook',line.middleware(config),(req,res)=>{....}

の処理を呼ぶことができる。
ただし、PCを閉じてしまうとこのngrokサーバも終了してしまい、常時動かすには不便。
ngrok.png

now

とてもデプロイが簡単なPaas。
サーバにアクセスされるとnow.jsonの設定の通り処理が実行される。
設定等はこちらを参照。
now.png

Heroku

Paas。Gitを使ってデプロイする必要がある。
今回公開しているアプリはHerokuを使っています。なので手順を下記に記載する。

初期化

npm init -y

必要なライブラリインストール

npm i body-parser express

.ignoreファイルの作成

# Dependency directories
node_modules/

# Optional npm cache directory
.npm

1番上の階層にProcfileの作成

web: node index.js

git

初期化

git init

herokuアプリを追加する。アプリケーションネームはお好きな名前を決める。

heroku create アプリケーションネーム

add -> commit -> pushをする

git add --a
git commit -m "commit"
git push heroku master

Nowで常時化

Nowを使って常時動くようにします。先述の通りNowはGitも不要で非常に簡単です。

準備

下記にあるようにngrokで動いていたモノが、nowでは正しい挙動をしない時があったので、前回記事から少し修正をした。
LINE BOTで天気を返すサンプルがngrokで動いてnowで動かない件

コード

linebot-now.js
'use strict';constexpress=require('express');constline=require('@line/bot-sdk');constPORT=process.env.PORT||3000;// 追加constaxios=require('axios');constconfig={channelSecret:'XXXXXXXXXXX',channelAccessToken:'XXXXXXXXXXX'};constapp=express();app.get('/',(req,res)=>res.send('Hello LINE BOT!(GET)'));//ブラウザ確認用(無くても問題ない)app.post('/webhook',line.middleware(config),(req,res)=>{// LINE上で入力されたconsole.log(req.body.events[0].message.text);//ここのif文はdeveloper consoleの"接続確認"用なので後で削除して問題ないです。if(req.body.events[0].replyToken==='00000000000000000000000000000000'&&req.body.events[1].replyToken==='ffffffffffffffffffffffffffffffff'){res.send('Hello LINE BOT!(POST)');console.log('疎通確認用');return;}Promise.all(req.body.events.map(handleEvent)).then((result)=>res.json(result));});constclient=newline.Client(config);asyncfunctionhandleEvent(event){if(event.type!=='message'||event.message.type!=='text'){returnPromise.resolve(null);}letmes=''// console.log(event.message.text);if(event.message.text.indexOf('')>-1){// ?を含んでいる場合にはwikiで検索したものを出して、含んでない場合はurlを返すvarstr=event.message.text;varresult=str.split('').join('');//?を取り除く処理mes=result+'の説明:';//wikiのbodyの前の一言awaitclient.replyMessage(event.replyToken,{type:'text',// text: event.message.text //実際に返信の言葉を入れる箇所text:mes});// このreturnはポイントreturngetBody(event.source.userId,result,mes);//wiki APIで取得できたらプッシュメッセージ}else{varresult=event.message.text;mes=result+'のURL:';//wikiのurlの前の一言awaitclient.replyMessage(event.replyToken,{type:'text',// text: event.message.text //実際に返信の言葉を入れる箇所text:mes});// このreturnはポイントreturngetUrl(event.source.userId,result);//wiki APIで取得できたらプッシュメッセージ}}constgetBody=async(userId,word,message)=>{constres=awaitaxios.get('http://wikipedia.simpleapi.net/api?keyword='+encodeURIComponent(word)+'&output=json');constitem=res.data;console.log(item);awaitclient.pushMessage(userId,{type:'text',text:item[0].body,});}constgetUrl=async(userId,word)=>{constres=awaitaxios.get('http://wikipedia.simpleapi.net/api?keyword='+encodeURIComponent(word)+'&output=json');constitem=res.data;// console.log(item); awaitclient.pushMessage(userId,{type:'text',text:item[0].url,});}(process.env.NOW_REGION)?module.exports=app:app.listen(PORT);console.log(`Server running at ${PORT}`);

Herokuで常時化

Herokuとは

参考
Herokuってなんなの?

ソースコード

linebot-heroku.js
'use strict';constexpress=require('express');constline=require('@line/bot-sdk');constPORT=process.env.PORT||3000;// 追加constaxios=require('axios');constconfig={channelSecret:'XXXXXXXX',channelAccessToken:'XXXXXXXX'};constapp=express();app.get('/',(req,res)=>res.send('Hello LINE BOT!(GET)'));//ブラウザ確認用(無くても問題ない)app.post('/webhook',line.middleware(config),(req,res)=>{//ここのif文はdeveloper consoleの"接続確認"用なので後で削除して問題ないです。if(req.body.events[0].replyToken==='00000000000000000000000000000000'&&req.body.events[1].replyToken==='ffffffffffffffffffffffffffffffff'){res.send('Hello LINE BOT!(POST)');console.log('疎通確認用');return;}Promise.all(req.body.events.map(handleEvent)).then((result)=>res.json(result));});constclient=newline.Client(config);functionhandleEvent(event){if(event.type!=='message'||event.message.type!=='text'){returnPromise.resolve(null);}letmes=''// console.log(event.message.text);if(event.message.text.indexOf('')>-1){// ?を含んでいる場合にはwikiで検索したものを出して、含んでない場合はurlを返すvarstr=event.message.text;varresult=str.split('').join('');//?を取り除く処理mes=result+'の説明:';//wikiのbodyの前の一言getBody(event.source.userId,result);//wiki APIで取得できたらプッシュメッセージ}else{varresult=event.message.text;mes=result+'のURL:';//wikiのurlの前の一言getUrl(event.source.userId,result);//wiki APIで取得できたらプッシュメッセージ}returnclient.replyMessage(event.replyToken,{type:'text',text:mes});}constgetBody=async(userId,word)=>{constres=awaitaxios.get('http://wikipedia.simpleapi.net/api?keyword='+encodeURIComponent(word)+'&output=json');constitem=res.data;// console.log(item); awaitclient.pushMessage(userId,{type:'text',text:item[0].body,});}constgetUrl=async(userId,word)=>{constres=awaitaxios.get('http://wikipedia.simpleapi.net/api?keyword='+encodeURIComponent(word)+'&output=json');constitem=res.data;// console.log(item); awaitclient.pushMessage(userId,{type:'text',text:item[0].url,});}app.listen(process.env.PORT||8080);// console.log(`Server running at ${PORT}`);

どんどん試そう

レンタルサーバを除いて、無料なのでお好きややり方でどんどん試していきましょう!

async awaitやPromiseはなぜ必要なのか?

$
0
0

非同期処理というものが必要な理由

Apache Http Serverは接続ごとにプロセスを生成するような仕組みです。このため、接続してくるクライアントがものすごく多くなると、メモリ領域を圧迫したり、レスポンス性能が壊滅的に低くなります。

この問題は、C10K問題(Client 10,000)と呼ばれ、これを解決するには、小さいリソースで多数のクライアントに対応できる仕組みが必要でした。

その具体的な解決策として出てきたのが、NginxやNode.jsです。今回はJavaScriptにおけるasync awaitに着目しますので、Node.jsについて焦点を当てます。

Node.jsでは、多数の処理要求をシングルスレッドで(少ないリソースで)高速に捌けるよう、ノンブロッキングIOを実現するアーキテクチャが採用されています。このあたりの詳しい説明は「Node.jsでのイベントループの仕組みとタイマーについて」で学びましょう。

要求を捌いてくれるスレッドは1つだけなわけですから、仮に全ての処理が同期的に行われてしまうと、レスポンス性能が非常に低くなります。クライアントからの処理を1つずつ順番に完了させていくような感じになるわけで、DBアクセスやファイルアクセスのようなIO処理で生じる待ち時間はボーッと待つだけです。当然ながら、その待ち時間で他の処理を進めた方が好ましいでしょう。

つまり、ある処理でIO等の待ちが発生する場合、他の処理に速やかにバトンを渡してあげる必要があるのです。これを実現する手段こそが非同期処理です。

私たちの書くコードで、待ちが発生するような重い処理を呼び出す場合、「この重い処理が終わったら、このコールバック関数を呼んでね」という感じでコールバック関数を指定しますよね。これは同期的な流れをいったん断ち切り、他の処理にバトンを渡しているということです。

以上が、非同期処理というものが必要な理由です。

async awaitやPromiseが必要な理由

非同期処理をうまく使うことで他の処理にバトンを渡す、という大前提の方針のなかで私たちはコードを書いていくことになります。

とはいえ、他の処理にバトンを渡して、すぐに次の処理(次の行のコード)を実行して良いかというと、多くの場合そんなことはないはずです。

例えば、DBを参照する処理であれば、その次にDBから取得したデータを使って何らかの処理をするでしょう。DBを参照する処理が完了するまで、次に行ってはいけないのです。

ここで必要なのは、「他の処理にバトンを渡しながらも、自分のコードでは次に進まずに止まる」ということです。

これを実現するのがPromiseや、Promiseを使いやすくしたasync awaitです。

Promiseやasync awaitについては、「JavaScript Promiseの本」で学びましょう。

私がPromiseやasync awaitを知った時は、「非同期処理を活用すべき環境のなかで、無理やり同期処理を実現していて、何が嬉しいのか分からないな・・」と感じたものです。これは、Promiseやasync awaitで待っている間、他の処理もブロックされている、という誤解から生じた考えでした。

Node.jsのノンブロッキングIOという環境の中で、他の処理にバトンを渡しつつも、自分のコードの処理では次に進まずに、重い処理が終わるまでジッと待つ。このような器用なことを実現するために、Promiseやasync awaitがあるわけです。

AWS LambdaでのNode.js

AWS Lambdaでは言語としてJavaScript(Node.js)を選ぶことができます。

Lambdaは当然ながら基本的には1つの処理要求を受けて、単発で動作するものですから、上記で述べたような「多数のクライアントからの処理要求を捌く」といった状況にはありません。

クライアントが1つしかいないような状況ですから、純粋に自分の処理が同期的に進むようにコードを書けば良いでしょう。

DBアクセスのような時間のかかるIO処理を呼び出すときには、誰にバトンを渡すわけでもないですが、Promiseやasync awaitを使って自分の処理が同期的に進むようにコードを書く、ということですね。

何というか、ちょっと無意味さを感じちゃいますね。

Viewing all 9069 articles
Browse latest View live