【webpack】webpackを使用してWebアプリケーションをバンドルする
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
についてはこちらを参照。
$ 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}())}])
どうやら「モジュールのバンドル」 とは
- 依存モジュールを一つの
js
にまとめること - スクリプトのミニマイズもする
- トランスパイルもする
といった操作のようです。
webpackを使ってみての感想ですが、「アプリの最適化」を行うのには素晴らしいツールだなと思いました。 アプリの複雑な依存関係を解決し一つのファイルにアプリのスクリプトをまとめることで、ビルド・デプロイするアプリを軽量化・高速化できるので、アプリの総仕上げとして良いのではないでしょうか。 (多くのフロントエンドCLIでもデフォルトで採用されているみたいですね)
参考文献
【Angular】Angular CLIで簡単にAngularプロジェクトの作成・デプロイ・テストを自動化する
Angular CLI
Angular CLIはGoogleが提供するフロントエンドフレームワーク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/
にアクセスすると、以下のようなデフォルトアプリが表示されます。
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
Karmaについてはこちらの記事参照。
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と比較すると、フレームワークがフルスタックなのもあり、プロジェクト生成時に入れるパッケージに悩まなくていいですね。
参考文献
【Node.js】npxでローカルにパッケージを一時的にインストールして実行する
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_modules
や package.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パッケージを手軽に、試しに実行したい場合などに便利です。
参考文献
【IBM COS】IBM Cloud Object Storageで署名付きURLを発行する
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でインストールすることができます。
$ brew install awscli
Homebrew、およびAWS CLIのインストールには下記も参考にしてください。
インストール後、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」となりリソースにアクセスすることができません。
また署名付きURLの有効期限は任意に設定することができます。今回は有効期限を一週間としています。 有効期限を過ぎた署名付きエンドポイントにアクセスすると、下記のようなレスポンスが返され、リソースにアクセスできなくなります。
このように、リソースへのアクセス手段を署名付きURL経由で提供することで、COSに保存されたリソースをより安全に共有できるようになります。
参考文献
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
※実行するためにはターミナルに対するコンピュータ制御権限を許可します
【Spring】環境変数で適用するapplication.propertiesを切り替える
複数の 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.properties
を application-dev.properties
のように環境別に用意し、application.properties
に以下のように記述することで application-dev.properties
の設定をアプリに適用させることができます。
application.properties
spring.profiles.active = dev
application-dev.properties
の dev
の部分には任意の名称を入れることができます。例えば、 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アプリケーション上に認証を実装する
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アカウントにログインしていない場合ログイン画面が表示されるので、自分のGoogleアカウントにログインしましょう。
次のページではFirebase上に作成したプロジェクト一覧が表示されます。 今回は初めてなので、下図のようなアイコンをクリックして新規にプロジェクトを作成していきます。
プロジェクト追加ダイアログが表示されます。 プロジェクト名を入力し、「測定管理者間のデータ保護条項に同意します。...」のチェックボックスにチェックを入れます。 これは少し前に有名になったGDPRに係る条項です。「「Google のサービス」のデータ共有設定」を無効にすれば、ここの条項に同意してもデータ提供を拒否することができます(ですが上のチェックマークに関しては、チェックが必須のようです)。
プロジェクト名を入力しチェックをつけおわったら、「プロジェクトを作成」ボタンをクリックします。
下のようなダイアログが出てくるので、しばらく待ちます。
ダイアログが下のようになったら、プロジェクト作成完了です。 「次へ」ボタンを押して、次に進みます。
以下のようなダッシュボード画面が表示されたら完了です。
アプリの認証機能を実装する
次に、アプリのコードを書いていきます!
プロジェクトの作成
今回はVue.jsを使ってフロントエンドアプリを構築していきます。 下記記事を参考に、プロジェクトを作成してください。
以降、上記記事で作成したプロジェクト上での開発を前提とします。
プロジェクトの認証機能を有効にする
先程開いたダッシュボードで認証機能を有効にする設定を行います。
ダッシュボード画面の「認証」のカードをクリックするか、右側メニューの「Authentication」をクリックします。
すると認証に関する設定画面が開くので、画面中の「ログイン方法を設定」をクリックします。
すると許可するログイン方法の設定一覧が開くので、「メール/パスワード」の項目の編集アイコンをクリック。
「有効にする」スイッチをONにし、「保存」をクリック。
これでメールアドレスとパスワードによる認証が実装可能になりました。
Firebase SDKをインストールする
WebアプリケーションでFirebase API を使用する方法は主に2つあります。
今回は npm
経由でインストールを行います。
作成したプロジェクトディレクトリに移動し、npm install firebase
を実行してください。
$ cd [作成したプロジェクトへのパス] $ npm install firebase --save
これでプロジェクトにSDKがインストールされました!
アプリケーション上にアカウント登録画面を作成する
「プロジェクトの作成」の項で作成したプロジェクトは、最初からデプロイ可能な状態となっています。というわけで、一旦アプリをデプロイしてみましょう!
# 作成したプロジェクト上で
$ npm run serve
するとVueのボイラーテンプレートアプリが起動します。
ここに新しく認証画面を追加してみましょう!
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 = ...
からの部分はアプリの設定情報が書かれています。これはダッシュボード画面中央の </>
アイコンから参照できます。
<script>
タグ内の記述をコピーし、 上記コードのように main.ts
に貼り付けてください。
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. アプリ実行
それでは、再度アプリをデプロイしてみます。
これでサインアップは成功です!
4. ユーザ情報の確認
登録したユーザ情報は、ダッシュボードの「Authentication」画面上で管理することができます。
ここでは登録したユーザ情報の編集や削除などができます。
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. アプリ再起動
では、この状態でアプリを起動してみます。
無事ログインできました!
Firebaseの認証はWebアプリだけでなく、iOSやAndroidなどのネイティブアプリにも使用可能です! ReactNativeですが過去記事にも掲載したので、参照していただけるとうれしいです!