Works by

Ren's blog

@rennnosuke_rk 技術ブログです

【webpack】webpackを使用してWebアプリケーションをバンドルする

f:id:rennnosukesann:20181225231948p:plain:w600

webpackとは

webPackとはモジュールバンドラと呼ばれるツールです。アプリーケーションが依存するjsモジュールを一つのjsファイルにまとめ上げ、依存関係の解決やトランスパイルを自動実行してくれます。内部的にモジュール同士の依存関係を表すグラフを自動で構築し、それに基づいて依存関係の解決をしてくれるので、設定ファイルなどなしに使用することができます(詳細な設定を記述したファイルを使用することもできます)。

・・・とはいったものの、「モジュールのバンドル」がいまいちピンと来なかったので、こちらチュートリアルを実践してみました。

以下、チュートリアルのメモになります。

webpackのインストール

まず、webpack対象となる node プロジェクトを作成します。

$ mkdir hello-webpack
$ cd hello-webpack
$ npm init -y

npm でwebpackと、webpackのCLIツールをインストールします。

$ npm install webpack webpack-cli --save-dev

webpackを試しに使用する

実験環境準備

webpackを使用する前に、テスト用のモジュールを作ってみます。

最初のディレクトリ構成は以下の通り。

$ ls
node_modules        package.json
package-lock.json

プロジェクトルートに以下のような index.html を作成します。HTMLを見ると、 index.html<script> タグ中でCDNによって茶刈 lodash.js スクリプトをロードしていることがわかります。

<!doctype html>
<html>
  <head>
    <title>Hello, webpack !!1!</title>
    <script src="https://unpkg.com/lodash@4.16.6"></script>
  </head>
  <body>
    <script src="./src/index.js"></script>
  </body>
</html>

次に内部ロジック記述用スクリプトである src/index.js を用意します。このスクリプトでは、先程 index.html でロードしていた lodash モジュールを使用しようとしているとします。が、 lodash に対する依存関係が暗黙的で、 _ を未定義のグローバル変数として参照してしまっています。

function component() {
  let element = document.createElement('div');
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  return element;
}

document.body.appendChild(component());

上記の index.js のような、参照できているようで参照できていない暗黙の依存関係は様々なバグを生んでしまいます。 このようなバグをなくすため、lodash をnpmでのパッケージインストール & import に変更してしまいましょう!

npm での lodash のインストール

先程用意した index.html を、新たに生成した /dist ディレクトリ配下に配置します。

dist
└── index.html

次に、 lodash をnpm経由でインストール。

$ npm install --save lodash

src/index.js を修正します。明示的に lodash を参照できるよう、下記一文をファイル先頭に追加します。

import _ from 'lodash';

dist/index.html を編集します。 <script> タグによる lodash のロードをやめ、 参照先スクリプトmain.js としました。これは後ほど使用するwebpackのバンドル後ファイルへの参照です。

webpackによるモジュールバンドル

上記の import 文によって、webpackが依存関係のグラフを自動作成可能になりました。

それでは、実際にwebpackでモジュールバンドルを行います。

npx コマンドを使用し、 webpack を実行します。

npx についてはこちらを参照。

rennnosukesann.hatenablog.com

$ npx webpack 
Hash: 53680323beef520a74cd
Version: webpack 4.28.2
Time: 445ms
Built at: 2018/12/25 23:10:03
  Asset      Size  Chunks             Chunk Names
main.js  1.03 KiB       0  [emitted]  main
Entrypoint main = main.js
[0] ./src/index.js 189 bytes {0} [built]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

webpackが成功し、 dist/main.js が生成されました!

!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t){document.body.appendChild(function(){let e=document.createElement("div");return e.innerHTML=_.join(["Hello","webpack"]," "),e}())}])

どうやら「モジュールのバンドル」 とは

といった操作のようです。


webpackを使ってみての感想ですが、「アプリの最適化」を行うのには素晴らしいツールだなと思いました。 アプリの複雑な依存関係を解決し一つのファイルにアプリのスクリプトをまとめることで、ビルド・デプロイするアプリを軽量化・高速化できるので、アプリの総仕上げとして良いのではないでしょうか。 (多くのフロントエンドCLIでもデフォルトで採用されているみたいですね)

参考文献

webpack - Getting Started

【Angular】Angular CLIで簡単にAngularプロジェクトの作成・デプロイ・テストを自動化する

f:id:rennnosukesann:20181224221629p:plain

Angular CLI

Angular CLIGoogleが提供するフロントエンドフレームワークAngularのためのCLIツールです。Angularプロジェクトの作成やデプロイ、テスト自動化などをより簡単に実行することができます。

インストール

npm でインストールします。

$ npm install -g @angular/cli

2018/12/24現在のバージョンだと7系のCLIがインストールされると思います。

$ ng --version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/
    

Angular CLI: 7.1.4
Node: 10.3.0
OS: darwin x64
Angular: 
... 

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.11.4
@angular-devkit/core         7.1.4
@angular-devkit/schematics   7.1.4
@schematics/angular          7.1.4
@schematics/update           0.11.4
rxjs                         6.3.3
typescript                   3.1.6
    

ng new : プロジェクトを作成する

ng new でAngularプロジェクトディレクトリを作成することができます。

 $ ng new

コマンドを実行すると、対話形式で下記項目について聞いてくるので、それぞれ入力or選択。

  • プロジェクト名
  • Angularルーティングの有効化
  • スタイルシートのフォーマットの選択
? What name would you like to use for the new workspace and initial project? my-angular-app
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS   [ http://sass-lang.com   ]

これでプロジェクトは完成です。

$ cd my-angular-app
$ ls
README.md       e2e         package-lock.json   src         tslint.json
angular.json        node_modules        package.json        tsconfig.json

生成したプロジェクトには、既にテスティングフレームワークやLinterなどのモジュールがインストールされています。Angularが2系より採用したTypeScriptも確認できます。

$ cat package.json
{
  "name": "my-angular-app",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "~7.1.0",
    "@angular/common": "~7.1.0",
    "@angular/compiler": "~7.1.0",
    "@angular/core": "~7.1.0",
    "@angular/forms": "~7.1.0",
    "@angular/platform-browser": "~7.1.0",
    "@angular/platform-browser-dynamic": "~7.1.0",
    "@angular/router": "~7.1.0",
    "core-js": "^2.5.4",
    "rxjs": "~6.3.3",
    "tslib": "^1.9.0",
    "zone.js": "~0.8.26"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.11.0",
    "@angular/cli": "~7.1.4",
    "@angular/compiler-cli": "~7.1.0",
    "@angular/language-service": "~7.1.0",
    "@types/node": "~8.9.4",
    "@types/jasmine": "~2.8.8",
    "@types/jasminewd2": "~2.0.3",
    "codelyzer": "~4.5.0",
    "jasmine-core": "~2.99.1",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~3.1.1",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "~1.1.2",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.4.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.11.0",
    "typescript": "~3.1.6"
  }
}

ng serve : Angularプロジェクトからアプリをローカルにデプロイする

ng serve で、ローカルサーバを立ち上げアプリをデプロイできます。

$ cd my-angular-app
$ ng serve
** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **
                                                                                          
Date: 2018-12-24T13:31:23.110Z
Hash: 5c43a53e1dad2b4bcc2c
Time: 11797ms
chunk {main} main.js, main.js.map (main) 11.5 kB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 223 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 6.08 kB [entry] [rendered]
chunk {styles} styles.js, styles.js.map (styles) 16.6 kB [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 3.67 MB [initial] [rendered]
ℹ 「wdm」: Compiled successfully.

http://localhost:4200/ にアクセスすると、以下のようなデフォルトアプリが表示されます。

f:id:rennnosukesann:20181224223348p:plain

ng generate : プロジェクトに必要なモジュールを追加する

ng generate を使用すると、classやdirective、serviceといったモジュールを特定のディレクトリ配下(デフォルトで src/apps )に生成してくれます。

 $ ng generate class HttpHelper
CREATE src/app/http-helper.ts (28 bytes)
$ ls src/app/http-helper.ts 
src/app/http-helper.ts

--force -f をつけると、既存のファイルを上書きして生成します。

$ ng generate class HttpHelper --force

ng lint : linterでコードを検査する

ng lint を実行すると既存のソースコードに対しLinterが起動し、lintの設定に則ったルールのもと文法ミスやアンチパターンを指摘してくれます。

 $ ng lint
Linting "my-angular-app"...


All files pass linting.
Linting "my-angular-app-e2e"...


All files pass linting.

ng test : 単体テスト実行

ng test で、プロジェクト内で単体テストを実行します。 デフォルトでは、テストランナーとしてKarmaによるテストが実行されます。 なおテストコード自体にはデフォルトでテスティングフレームワークJasmineが使われているようです。

$ ng test
 11% building modules 9/9 modules 0 active24 12 2018 22:47:10.440:WARN [karma]: No captured browser, open http://localhost:9876/
24 12 2018 22:47:10.447:INFO [karma-server]: Karma v3.1.4 server started at http://0.0.0.0:9876/
24 12 2018 22:47:10.447:INFO [launcher]: Launching browsers Chrome with concurrency unlimited
24 12 2018 22:47:10.464:INFO [launcher]: Starting browser Chrome
24 12 2018 22:47:18.729:WARN [karma]: No captured browser, open http://localhost:9876/    
24 12 2018 22:47:19.418:INFO [Chrome 71.0.3578 (Mac OS X 10.14.1)]: Connected on socket U_QKpHniSB3g0GIZAAAA with id 75357283
Chrome 71.0.3578 (Mac OS X 10.14.1): Executed 3 of 3 SUCCESS (0.288 secs / 0.245 secs)
TOTAL: 3 SUCCESS
TOTAL: 3 SUCCESS

f:id:rennnosukesann:20181224224926p:plain

Karmaについてはこちらの記事参照。

rennnosukesann.hatenablog.com

ng e2e E2Eテスト実行

ng e2e で、E2Eテストが開始されます。

$ ng e2e
** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **
                                                                                          
Date: 2018-12-24T13:51:46.733Z
Hash: 5c43a53e1dad2b4bcc2c
Time: 11117ms
chunk {main} main.js, main.js.map (main) 11.5 kB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 223 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 6.08 kB [entry] [rendered]
chunk {styles} styles.js, styles.js.map (styles) 16.6 kB [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 3.67 MB [initial] [rendered]
[22:51:47] I/file_manager - creating folder /Users/aa367417/hoge/my-angular-app/node_modules/protractor/node_modules/webdriver-manager/selenium
[22:51:47] I/config_source - curl -o/Users/aa367417/hoge/my-angular-app/node_modules/protractor/node_modules/webdriver-manager/selenium/chrome-response.xml https://chromedriver.storage.googleapis.com/
ℹ 「wdm」: Compiled successfully.
[22:51:48] I/downloader - curl -o/Users/aa367417/hoge/my-angular-app/node_modules/protractor/node_modules/webdriver-manager/selenium/chromedriver_2.45.zip https://chromedriver.storage.googleapis.com/2.45/chromedriver_mac64.zip
[22:51:49] I/update - chromedriver: unzipping chromedriver_2.45.zip
[22:51:49] I/update - chromedriver: setting permissions to 0755 for /Users/aa367417/hoge/my-angular-app/node_modules/protractor/node_modules/webdriver-manager/selenium/chromedriver_2.45
[22:51:50] I/launcher - Running 1 instances of WebDriver
[22:51:50] I/direct - Using ChromeDriver directly...
Jasmine started

  workspace-project App
    ✓ should display welcome message

Executed 1 of 1 spec SUCCESS in 2 secs.
[22:51:58] I/launcher - 0 instance(s) of WebDriver still running
[22:51:58] I/launcher - chrome #01 passed

簡単ですが、Angular CLIをいじってみました。 既に使用しているVue CLIと比較すると、フレームワークフルスタックなのもあり、プロジェクト生成時に入れるパッケージに悩まなくていいですね。

参考文献

Angular CLI

github.com

【Node.js】npxでローカルにパッケージを一時的にインストールして実行する

f:id:rennnosukesann:20181223181823p:plain

npx

npx はローカルにインストールしたnpmパッケージバイナリを直接実行できるコマンドです。npm5.2.0より導入されました。

このnpx にはパッケージを一時的にだけインストールしてその機能を実行する機能が備わっているので、 今回はその機能について紹介します。

npx のインストール

まずは npm 自体のアップデートを行います。

$ npm install -g npm 

5.2.0以上であることを確認。

$ npm --version
6.2.0

確認できたら、 npx をインストールします。

$ npm install -g npx

インストールしていないパッケージを実行

ローカルにインストールしていないパッケージを実行することができる npx ですが、一旦はパッケージのインストールが走ります。 が、コマンド実行後インストールされたままにならず削除されるので、ローカルの node_modulespackage.json を汚しません。

以下はAngular CLI のバージョンチェックを実行している例です。 

$ npx @angular/cli --version


     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/
    

Angular CLI: 7.1.4
Node: 8.11.3
OS: darwin x64
Angular: 
... 

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.11.4
@angular-devkit/core         7.1.4
@angular-devkit/schematics   7.1.4
@schematics/angular          7.1.4
@schematics/update           0.11.4
rxjs                         6.3.3
typescript                   3.1.6

実行したパッケージのコマンドを実行してみるとできないことがわかります。 パッケージがインストールされず、そのまま破棄されていることがわかります。

# インストールはされていない
$ ng
zsh: correct 'ng' to 'bg' [nyae]? n

npmパッケージを手軽に、試しに実行したい場合などに便利です。

参考文献

www.npmjs.com

【IBM COS】IBM Cloud Object Storageで署名付きURLを発行する

f:id:rennnosukesann:20181216025439p:plain:w200

IBM Cloud Object Storageとは

IBM Cloud Object Storage(COS)はIBMクラウド上で提供するストレージサービスです。IBM Cloud上でAWSのS3のようなオブジェクトベースでリソースを保存できるストレージ環境を提供します。IBM Cloud上でサービスとして提供されているので、クラウド上に立ち上げたアプリケーションサーバと連携して画像や音声の保存用ストレージとして利用する・・・といったことが可能です。それだけでなく、オンプレミス環境でホスト型クラウド環境を構築することでもCOSを利用することができます。

COSが使われるシーン

多くの場合、Webアプリケーションやシステムで使われるDB(RDBMS)では、文字列や数値といったデータと、それらのデータからなるいわゆる構造化データが扱われます。例えばRDBの場合、テーブルに挿入されるレコードが構造化データになります。このような構造化データはスキーマによって明確に定義されるため、RDBで管理しやすい傾向にあります。

しかし、画像や音声といったいわゆる非構造化データは、構造化されたデータを扱うシステムとはあまり相性がよくありません。例えば画像バイナリをRDBに保存する場合、数MBに及ぶバイナリを文字列(Base64など)に変換してどこかのテーブルの1カラムの値として保存する、といったことなどをやらなければいけません。このような実装は、RDBの設計にいびつな構造をもたらします。

そこで、そのような非構造化データは一旦別の場所に追いやり、そのデータへのアクセスポイントをRDBレコードのカラム値として保存するやり方が考えられます。そのような場合にCOSが威力を発揮します。

COSはKVSライクにデータを保存するオブジェクトベースのストレージです。特定のオブジェクトデータに一意なキーを対応付け、それをもとにアクセスポイントを発行してデータの更新/参照を可能にします。例えば画像であれば、COSに保存した画像のオブジェクトキーからURLを発行することができます。

IBM COSの署名付きURL

COSに保存したリソースを公開するとき、特定サービスのユーザに対してのみ限定公開したい場合があると思います。COSのエンドポイントをprivateにする方法もありますが、スマートフォンなどパブリックなネットワークに接続するクライアントに対してデータを提供したい場合など、適用しにくい場合も考えられます。

そのような場合、署名付きURLが使えます。COSの設定でエンドポイントに対するアクセスを原則禁止としつつ、署名付きのURL経由でのみリソースにアクセスさせるといったことが可能なため、パブリックなネットワークに接続するクライアントに対してもリソースの提供に対応できます。

署名付きURLの発行

AWS CLI

署名付きURLは、AWS CLIを利用して発行することができます。 Macの場合、 Homebrewでインストールすることができます。

IBM Cloud Docs

$ brew install awscli

Homebrew、およびAWS CLIのインストールには下記も参考にしてください。

brew.sh

docs.aws.amazon.com


インストール後、aws configure でCOSエンドポイントの設定を行います。

$ aws configure
AWS Access Key ID [None]: {Access Key ID}
AWS Secret Access Key [None]: {Secret Access Key}
Default region name [None]: {Provisioning Code}
Default output format [None]: json

設定後、下記コマンドを実行することで署名付きURLを発行できます。 --endpoint-url には署名付きURLを跛行したいしたいリソースのCOSエンドポイントを、 {bucketName} にはリソースのバケット名、 {objKey} にはオブジェクトキーを入力してください。

$ aws --endpoint-url=https://{endpoint} s3 presign s3://{bucketName}/{objKey}

バケットはCOS内で一意な保存領域を表し、複数のバケットを一つのCOSサービス内に生成することでリソースの保存領域を論理的に分割することができます。オブジェクトキーはCOS内リソースを一意に識別するためのキーになります。

また、下記のように署名付きURLには有効期限を任意に設定することができます。

$ aws --endpoint-url=https://{endpoint} s3 presign s3://bucket-1/new-file --expires-in 604800000

Java

COSのSDKを利用して、プログラム中でも署名付きURLを発行することができます。 今回はJavaを題材としてコード例を示します。

build.gradle
dependencies {
    ...  
    implementation('com.ibm.cos:ibm-cos-java-sdk:latest.release')
    ...
}
Java
private String getPreSignedUrl(String bucketName, String objectKey) {

    // COS認証情報(実際の運用では環境変数等で外出ししてください)
    String accessKey = "XXXXXX";
    String secretKey = "XXXXXXXXX";
    String endpoint = "XXXXXXXXXX";
    String region = "XXXXXXXX";

    // 署名有効期限までの時間
    long signExpiration = 604800000; // 1week

    // 認証情報
    AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);

    // クライアント設定
    ClientConfiguration clientConfig = new ClientConfiguration();
    clientConfig.setProtocol(Protocol.HTTPS); // プロトコル
    clientConfig.setConnectionTimeout(10000); // 接続タイムアウト(ms)

    // COSエンドポイント設定
    EndpointConfiguration endpointConfiguration = new EndpointConfiguration(endpoint, region);

    // クライアント生成
    AmazonS3 client = AmazonS3ClientBuilder.standard()
        .withCredentials(new AWSStaticCredentialsProvider(credentials)).withClientConfiguration(clientConfig)
        .withEndpointConfiguration(endpointConfiguration).build();

    // 署名有効期限
    Date expiration = new Date();
    long msec = expiration.getTime() + signExpiration;
    expiration.setTime(msec);

    // 署名つきURL生成
    GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, objectKey);
    generatePresignedUrlRequest.setMethod(HttpMethod.GET);
    generatePresignedUrlRequest.setExpiration(expiration);
    String url = client.generatePresignedUrl(generatePresignedUrlRequest).toString();
 
    return url;   
}

上記メソッドでは既にCOS上に保存されているリソースが置かれているバケット名と、リソースオブジェクトキーを受け取り、署名付きURLを生成しています。

署名付きURLによるリソースへのアクセス

生成した署名付きURLは以下のようになります(CredentialやSignatureなど、一部ぼかした表現にしています)。

https://hogehoge.us-south.objectstorage.softlayer.net/fugafuga.JPEG?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20181222T101615Z&X-Amz-SignedHeaders=host&X-Amz-Expires=604800&X-Amz-Credential=[Credential...]&X-Amz-Signature=[Signature...]

署名付きURLを生成すると、以下のようなパラメータがURLに付加されます。これらはURLに対する署名情報を表しています。

X-Amz-Algorithm ダイジェスト計算のためのハッシュアルゴリズム
X-Amz-Date URL生成時刻
X-Amz-SignedHeaders 署名対象ヘッダ情報
X-Amz-Expires URL署名有効期限
X-Amz-Credential 認証情報
X-Amz-Signature 署名

これら署名情報のないURLにアクセスするとHTTPステータスコード403が返され、「Access Denied」となりリソースにアクセスすることができません。

f:id:rennnosukesann:20181222193737p:plain

また署名付きURLの有効期限は任意に設定することができます。今回は有効期限を一週間としています。 有効期限を過ぎた署名付きエンドポイントにアクセスすると、下記のようなレスポンスが返され、リソースにアクセスできなくなります。

f:id:rennnosukesann:20181222193800p:plain

このように、リソースへのアクセス手段を署名付きURL経由で提供することで、COSに保存されたリソースをより安全に共有できるようになります。

参考文献

www.ibm.com

www.ibm.com

https://console.bluemix.net/docs/services/cloud-object-storage/hmac/presigned-urls.html#create-a-presigned-url

Macターミナルでカレントディレクトリを維持したまま新規タブを開くコマンド作った

script

macho(script)
#!/bin/bash

PREV_WD=`pwd`
osascript <<EOF
tell app "Terminal"
   tell application "System Events" to keystroke "t" using command down
end tell
EOF
cd $PREV_WD
unset PREV_WD

設定

実行設定は下記の通り

# シェルコマンド実行パスの通ったディレクトリに移動
$ pwd # /usr/local/bin
# 権限を付与
$ chmod +x macho

実行

$ macho

※実行するためにはターミナルに対するコンピュータ制御権限を許可します f:id:rennnosukesann:20181221202617p:plain

【Spring】環境変数で適用するapplication.propertiesを切り替える

f:id:rennnosukesann:20181220183020p:plain

複数の application.properties の切り替え

Springフレームワークでは、アプリケーション内で使う設定を application.properties に書き込み、実行時に設定内容を反映させることができます。

application.properties
// DB接続先
spring.datasource.url=https://hoge.fuga...
spring.datasource.driver-class-name=db-hoge...
spring.datasource.username=hogeDB
spring.datasource.password=hogehoge

// Spring Batch のアプリ起動時の設定
spring.batch.job.enabled=false

...

application.properties には実行環境ごとに変化しやすいアプリ内で使用できる変数を記述しておくことで、アプリケーションの実装に変更を加えずにDBの接続先やロガーのデバッグレベルの設定などを変更することができます。

このような設定情報のプリセットを開発/UAT/本番など複数の環境用に用意して、各環境ごとに切り替えたいという場合がよくあります。

Springでは、適用したい application.propertiesapplication-dev.properties のように環境別に用意し、application.properties に以下のように記述することで application-dev.properties の設定をアプリに適用させることができます。

application.properties
spring.profiles.active = dev

application-dev.propertiesdev の部分には任意の名称を入れることができます。例えば、 application-hoge.properties ファイルの設定を適用したければ、application.properties に以下を追加します。

spring.profiles.active = hoge

これで application.properties の切り替えが行えるようになりました!

application.properties ファイルの切り替えを環境変数を用いて行う

ただ、デプロイごとにspring.profiles.active の値を変えるのは面倒です。また、application.properties をバージョン管理してる場合にいちいち変更差分が出て面倒です。

そこで、環境変数application.properties の切り替えを委譲します。 環境変数であれば、Dockerの Dockerfile やCloudFoundlyの manifest.yml など、個別のデプロイ環境用Configファイルに使用すべき application.properties を記述できるようになります。

環境変数 SPRING_PROFILE_ACTIVE があるとき、 application.properties 内では以下のように参照することができます。

application.properties
spring.profiles.active = ${SPRING_PROFILE_ACTIVE:local}

: の右隣の local はデフォルト値です。 SPRING_PROFILE_ACTIVE が設定されていない場合に、 local の文字列を spring.profiles.active に格納します。

これで環境変数経由で application.properties を切り替えられるようになりました。

【Firebase】Firebase(+Vue.js)でWebアプリケーション上に認証を実装する

f:id:rennnosukesann:20181216205841p:plain

Frebaseとは

Firebaseは、Googleが提供するモバイルアプリケーション開発のプラットフォームサービスです。mBaas(Mobile Backend as a Service) とも呼ばれ、モバイルアプリケーションのバックエンド機能を提供します。例えばアプリケーションの構築に必要なネットワークやストレージ、サーバといったインフラ基盤や、後述する認証や永続化ロジックなどといったバックエンド機能をFirebaseが提供してくれます。

Firebaseの何がいいのか

Firebaseを使うことで、上記のようなインフラ/バックエンドの機能をFirebaseに任せることができ、アプリエンジニアはフロントの実装に集中することができます。

例えばインフラ面では、特にサーバーの立ち上げ作業などを行うことなく、Firebaseが提供する環境にアプリをそのままデプロイすることができます。またアプリの監視や分析なども、Firebase内のサービスで提供されています。

バックエンド面としてもFirebaseのサポートは厚く、例えばストレージサービスも含めた認証の仕組みを持っており、フロントエンドはAPIコールだけで認証ロジックを実装することができます。認証は実装に手間がかかるだけではなく、自前実装によるバグの混入によってセキュリティリスクにもなりえるので、こういったサポートはとても嬉しいですね。もちろん認証後のトークンIDの運用などもあるので、セキュリティリスクがゼロになるわけではないのですが・・・。

このように、フロントエンド開発したいけどバックエンド実装に時間がかけれられない、インフラの知識に自信がない・・・といった方でも、手早くサービスを立ち上げることができる強力なサービスです。アプリリリースまでのスピード感を特に重視されている方にとって、大きな味方となってくれることでしょう。

Firebaseとブラウザベースアプリケーション

Firebaseはモバイルアプリを対象に作られている節がありますが、ブラウザで動作することが前提のWebアプリでも使用することはできます。Firebaseは、ブラウザベースのモバイルアプリも想定してSDKを提供してくれています。

FirebaseでWebアプリケーション上にベーシック認証機能を実装する

Firebaseの提供する認証機能を使って、実際にブラウザ上アプリでの認証機能を実装してみましょう!

SDKの導入

はじめに、Firebaseの提供するAPIを使用するためのSDKを導入します。

firebase.google.com

Firebase公式のページの「スタートガイド」ボタンをクリックします。

f:id:rennnosukesann:20181217201021p:plain

このとき、Googleアカウントにログインしていない場合ログイン画面が表示されるので、自分のGoogleアカウントにログインしましょう。

f:id:rennnosukesann:20181217203028p:plain

次のページではFirebase上に作成したプロジェクト一覧が表示されます。 今回は初めてなので、下図のようなアイコンをクリックして新規にプロジェクトを作成していきます。

f:id:rennnosukesann:20181217200327p:plain

プロジェクト追加ダイアログが表示されます。 プロジェクト名を入力し、「測定管理者間のデータ保護条項に同意します。...」のチェックボックスにチェックを入れます。 これは少し前に有名になったGDPRに係る条項です。「「Google のサービス」のデータ共有設定」を無効にすれば、ここの条項に同意してもデータ提供を拒否することができます(ですが上のチェックマークに関しては、チェックが必須のようです)。

プロジェクト名を入力しチェックをつけおわったら、「プロジェクトを作成」ボタンをクリックします。

f:id:rennnosukesann:20181217200910p:plain

下のようなダイアログが出てくるので、しばらく待ちます。

f:id:rennnosukesann:20181217200649p:plain

ダイアログが下のようになったら、プロジェクト作成完了です。 「次へ」ボタンを押して、次に進みます。

f:id:rennnosukesann:20181217201118p:plain

以下のようなダッシュボード画面が表示されたら完了です。

アプリの認証機能を実装する

次に、アプリのコードを書いていきます!

プロジェクトの作成

今回はVue.jsを使ってフロントエンドアプリを構築していきます。 下記記事を参考に、プロジェクトを作成してください。

rennnosukesann.hatenablog.com

以降、上記記事で作成したプロジェクト上での開発を前提とします。

プロジェクトの認証機能を有効にする

先程開いたダッシュボードで認証機能を有効にする設定を行います。

ダッシュボード画面の「認証」のカードをクリックするか、右側メニューの「Authentication」をクリックします。

f:id:rennnosukesann:20181219202506p:plain

f:id:rennnosukesann:20181219202552p:plain

すると認証に関する設定画面が開くので、画面中の「ログイン方法を設定」をクリックします。

f:id:rennnosukesann:20181219202742p:plain

すると許可するログイン方法の設定一覧が開くので、「メール/パスワード」の項目の編集アイコンをクリック。

f:id:rennnosukesann:20181219202835p:plain

「有効にする」スイッチをONにし、「保存」をクリック。

f:id:rennnosukesann:20181219202909p:plain

これでメールアドレスとパスワードによる認証が実装可能になりました。

Firebase SDKをインストールする

WebアプリケーションでFirebase API を使用する方法は主に2つあります。

  • HTML上でCDN経由でSDKjs ファイルをインポートする
  • npm yarn などのパッケージ管理システム経由でSDKパッケージをインストールする

今回は npm 経由でインストールを行います。

作成したプロジェクトディレクトリに移動し、npm install firebase を実行してください。

$ cd [作成したプロジェクトへのパス]
$ npm install firebase --save

これでプロジェクトにSDKがインストールされました!

アプリケーション上にアカウント登録画面を作成する

「プロジェクトの作成」の項で作成したプロジェクトは、最初からデプロイ可能な状態となっています。というわけで、一旦アプリをデプロイしてみましょう!

# 作成したプロジェクト上で
$ npm run serve

するとVueのボイラーテンプレートアプリが起動します。

f:id:rennnosukesann:20181218224212p:plain

ここに新しく認証画面を追加してみましょう!

1. Firebase初期化処理の追加

まず、作成したプロジェクト中の src/main.ts を以下のように編集します。

src/main.ts
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';

// ここから追加
import firebase from 'firebase';

// firebase初期化
const config = {
  apiKey: "<API_KEY>",
  authDomain: "<PROJECT_ID>.firebaseapp.com",
  databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
  projectId: 'XXXXXXXXXX',
  storageBucket: "<BUCKET>.appspot.com",
  messagingSenderId: 'XXXXXXXX',
};
firebase.initializeApp(config);

// ここまで追加

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount('#app');

追加した箇所のうち const config = ... からの部分はアプリの設定情報が書かれています。これはダッシュボード画面中央の </> アイコンから参照できます。

f:id:rennnosukesann:20181217202237p:plain

f:id:rennnosukesann:20181217204348p:plain

<script> タグ内の記述をコピーし、 上記コードのように main.ts に貼り付けてください。

f:id:rennnosukesann:20181217202303p:plain

2. サインアップ画面の作成

次に、サインアップ画面部分のコードを作成していきます。 Vue.jsは、アプリの画面を構成する部品を「UIコンポーネント」として扱います。 UIコンポーネントはUI、いわゆる画面表示上の要素と、それに紐づくロジックのまとまりです。Vue.jsでは画面表示を定義するコード(主にHTML) と画面表示に紐づくロジックを定義するスクリプト(主にJavaScriptやTypeScriptなど)、そして画面定義に対するスタイル定義(CSSなど)を .vue ファイルとして一箇所に定義でき、それをUIコンポーネント単位でそれぞれ記述することができます。

それでは、サインアップ画面のUIコンポーネント src/views/Auth.vue を作成していきましょう。

本当は再利用性・保守性などを考えつつ、画面を意味のある単位に分けてUIコンポーネントとして作成していくのですが、今回は簡単のため一画面丸々UIコンポーネントとして作成します。

src/views/Auth.vue
<template>
  <div class="auth">
    <p>Sign Up</p>
    <input v-model="email" placeholder="E-mail">
    <input type="password" v-model="password" placeholder="password">
    <div class="button" @click="signup">Sign Up</div>
  </div>
</template>

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

@Component
export default class Auth extends Vue {

  private email: string = '';
  private password: string = '';

  private signup() {
    firebase
      .auth()
      .createUserWithEmailAndPassword(this.email, this.password)
      .then(() => {
        alert('Success to Sign Up!');
      })
      .catch((error) => {
        alert('Failed to Sign Up...\n error code : ' + error.code + ', error message : ' + error.message);
      });
  }
}
</script>

<style lang="scss">

.auth {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.auth input {
  width: 200px;
  height: 30px;
  margin: 10px;
  padding: 0 10px;
  border: solid 1px #999999;
  border-radius: 5px;
}

.auth .button {
  width: 80px;
  height: 30px;
  margin: 10px;
  padding: 7px 0 0 0;
  color: white;
  background-color: #999999;
  border-radius: 5px;
}

.auth .button:hover{
  opacity: 0.5;
  cursor: pointer;
}

</style>
UI部分

src/views/Auth.vue のうち、画面定義、すなわちViewに該当する部分が以下になります。メールアドレス・パスワードの入力欄と、Signupボタンを一つ用意しています。 v-model は「ディレクティブ」と呼ばれるVueの機能の一つで、ロジック中の変数にビューへの入力をバインドします。ここでは Auth クラスの email password にそれぞれの入力欄に入力された値が紐付いています。 button にはイベントハンドラとして Auth クラスのメソッド signup() が設定されています。

<template>
  <div class="auth">
    <p>Sign Up</p>
    <input v-model="email" placeholder="E-mail">
    <input type="password" v-model="password" placeholder="password">
    <div class="button" @click="signup">Sign Up</div>
  </div>
</template>

ロジック部分

画面定義部分の下には内部ロジックのスクリプトが記述されています。 注目していただきたいのは、 class Auth のメソッド signup() です。こちらのメソッドはSignupボタンのクリックイベントハンドラとなっています。このメソッドではFirebase SDKのモジュールである firebase を参照して、ユーザ登録用のAPIを呼び出しています。

ユーザ登録に必要な処理はたったこれだけです。firebase.().auth().createUserWithEmailAndPassword() メソッドをメールアドレス・パスワードを渡して呼び出すと、 非同期に認証処理を実行します。このメソッドからは Promise オブジェクトが返ってくるので、 then() に成功時処理を、 catch に失敗時処理を渡すことができます。

    firebase
      .auth()
      .createUserWithEmailAndPassword(this.email, this.password)
      .then(() => {
        alert('Success to Sign Up!');
      })
      .catch((error) => {
        alert('Failed to Sign Up...\n error code : ' + error.code + ', error message : ' + error.message);
      });

2. Auth.vue動線の追加

UIコンポーネントを作成したら、今度はこのUIコンポーネントを表示するための動線を作成します。

src/router.ts を以下のように編集します。

src/router.ts
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';
import Auth from './views/Auth.vue';

Vue.use(Router);

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
    },
// ここから追加
    {
      path: '/auth',
      name: 'auth',
      component: Auth,
    },
// ここまで追加
  ],
});

src/router.ts はアプリのルーティング情報、すなわちどのパスに遷移したらどのページを表示するか、といった部分を制御します。 今回は /auth パスを追加し、ページ遷移時には src/view/Auth.vue で定義した内容を表示するようにしました。

これでアドレスバーに [アプリのベースパス]/auth を追加すればサインアップが表示されるようになったのですが、ちょっと不便です。 そこで、「About」「Home」画面のように、画面上部にリンクを作成してみましょう。

src/App.vue のHTML部分を以下のように書き換えます。

src/App.vue
<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link> |
      <router-link to="/auth">Signup</router-link>
    </div>
    <router-view/>
  </div>
</template>

新しく追加したのは <router-link> タグです。 ルーティング情報に追加したパスを to 属性に指定することで、そのページへ遷移してくれるリンクを作成してくれます。

3. アプリ実行

それでは、再度アプリをデプロイしてみます。

f:id:rennnosukesann:20181219212934g:plain

これでサインアップは成功です!

4. ユーザ情報の確認

登録したユーザ情報は、ダッシュボードの「Authentication」画面上で管理することができます。

f:id:rennnosukesann:20181219220727p:plain

ここでは登録したユーザ情報の編集や削除などができます。

5. ログイン画面の作成

サインアップ画面と同じように、次はログイン画面を作成していきましょう!

せっかくなので、先程の Auth.vue を使いまわします。 Auth クラスに、新たに以下のメソッドを追加します。

  private login() {
    firebase
      .auth()
      .signInWithEmailAndPassword(this.email, this.password)
      .then(() => {
        alert('Success to Log In!');
      })
      .catch((error) => {
        alert('Failed to Log In...\n error code : ' + error.code + ', error message : ' + error.message);
      });
  }

signInWithEmailAndPassword() メソッドはメールアドレス・パスワードによる認証を実行します。 使用方法は登録時と同じで、引数にメールアドレス・パスワードを渡して呼び出すだけです。 非同期処理の結果は then catch で受け取ります。

そしてHTML部分を以下のように編集します。

<template>
  <div class="auth">
    <p v-if="isLogin">Log In</p>
    <p v-else>Sign Up</p>
    <input v-model="email" placeholder="E-mail">
    <input type="password" v-model="password" placeholder="password">
    <span class="check"><input type="checkbox" v-model="isLogin"/><p>LogIn</p></span>
    <div class="button" v-if="isLogin" @click="login">Log In</div>
    <div class="button" v-else @click="signup">Sign Up</div>
  </div>
</template>

チェックボックスを用意し、Login用のフォームに切り替えられるようにしました。 (実際のアプリではもっとユーザーが混乱しないように作るべきですが)

v-if はVueのディレクティブで、渡した値の条件がtrueのときに要素を追加します。 v-else は対となる v-if 条件が成り立たないときに要素を追加します。

CSSも少し追加。

.auth .check {
  display: flex;
  align-items: center;
  width: 100px;
}

6. アプリ再起動

では、この状態でアプリを起動してみます。

f:id:rennnosukesann:20181219225232g:plain

無事ログインできました!


Firebaseの認証はWebアプリだけでなく、iOSAndroidなどのネイティブアプリにも使用可能です! ReactNativeですが過去記事にも掲載したので、参照していただけるとうれしいです!

rennnosukesann.hatenablog.com

参考文献

Add Firebase to your JavaScript Project  |  Firebase