Rx.jsでTinder風UIを実装する
業務でTinder風UIを作るオーダーが来たので,勉強も兼ねてRx.jsで作ることにした.
コードと処理の流れ
下記のコードはRx.js ver5.8.8で動作を確認しています. v6.0.0では動作しないので適宜読み替えてください.
また,モバイルでの動作を想定して記述しています.
const tolerance = 200; const windowWidth = window.innerWidth const imageElem = document.querySelector('.characterImage') // dom // 現在のスワイプ位置を取得 const touchstart$ = Rx.Observable.fromEvent(imageElem, 'touchstart') .switchMap(startEvent => Rx.Observable.fromEvent(imageElem, "touchend") .map(e => e.changedTouches[0].pageX) ) touchstart$ .do(val => { // スワイプ位置を判断して画像を振り分ける if(val > windowWidth - tolerance) { imageElem.classList.add('rotate-right') } else if(val < windowWidth / 2.0 - tolerance) { imageElem.classList.add('rotate-left') } }) .delay(1000) .subscribe(val => { imageElem.classList.remove('rotate-right') imageElem.classList.remove('rotate-left') })
対象のDOMをfromEventで監視して,touchend時の座標を基に判定する. torelanceをいじることで動作をもう少し過敏にしたりできる.
subscribeではサンプル用に画像の復帰処理をしているが,画像の追加変更も任意で行える.
所感
まだRx.jsの勉強中なので掴めていないところが多々あるが,一旦は目標のものが出来上がった. Rx.jsは出来ることがたくさんあって使い所が分かっていなかったり,そもそも書き方がまだおぼつかなかったり… 使いながら覚えていくしかなさそう.
上記コードの指摘等あればプルリクを投げてもらえると嬉しいです.
サンプルプロジェクト
https://github.com/heimusu/rxtinder
参考
GulpでCloudFront + S3環境へのフロントエンドデプロイを自動化する
CloudFrontに紐付いたS3に静的ファイル一式を置いた構成はテッパンだと思う. この場合,静的ファイルを更新するためには 1. S3にファイルをアップロードする 2. CFをInvalidateする の手順が必要となる. 滅多に更新が発生しないならともかく,絶賛開発中のプロジェクトの場合にこの操作を何度も手動で繰り返したくない.
本来ならばCIを設定したりwebpackでbuildついでに実行するなどありそうだが,オーソドックスにgulpタスクを書いてみた.
パッケージインストール
$ yarn add gulp gulp-awspublish gulp-cloudfront-invalidate
gulpタスクの記述
const gulp = require('gulp'); const awspublish = require('gulp-awspublish'); const cloudfront = require('gulp-cloudfront-invalidate'); // S3にデプロイするタスク // dev環境にデプロイする gulp.task('deploy', function() { const key = { accessKeyId: '...', secretAccessKey: '...', region: "ap-northeast-1", params: { "Bucket": "bucket-name", } } const publisher = awspublish.create(key); // deployするもの gulp.src(['**/*', '!node_modules/**/*', '!gulpfile.js', '!package.json', '!README.md', '!yarn.lock', '!.babelrc' ]) .pipe(publisher.publish()) .pipe(awspublish.reporter()); }); // CFをInvalidateする const cfInvalidateSettings = { distribution: '...', paths: ['/*'], accessKeyId: '...', secretAccessKey: '...', wait: true } gulp.task('invalidate', function() { return gulp.src('*') .pipe(cloudfront(cfInvalidateSettings)); })
実行
$ gulp deploy && gulp invalidate
これで,指定したS3に静的ファイルを設置(存在しないファイルは新規に設置,存在するファイルは差分があれば更新)した後に指定したCFをinvalidateしてくれる. あとは個々の案件に併せてタスクをカスタマイズして,package.jsonのscriptsに良い感じで書いておく. 上記の設定だとinvalidateの完了までwaitする設定になっているが,外すことも出来る.個人的にはしっかりinvalidateが完了したかを見届けたいのでwaitすることにしている.
艦これをelectronでwrapしてアプリめいて起動する
Windowsがメイン環境な提督たちは,提督業も忙しい!を使うと快適にプレイできる. 一方でMacな提督たちは,(僕が知らないだけだと思うけど)基本的にブラウザ上 or Extensionな環境でプレイしていると思う.
ガチ勢ではない僕にとってはそれほど不便ではないのだけれど,Macを使っていてCommand + Tabでアプリケーションを切り替えたりすると, 艦これだけが立ち上がっているWindowも一緒に最前面に出てきてしまう. じゃあSafariとか使えよ.という話ではあるが.
せっかくなのでElectronでWrapして,アプリめいて起動させてやろうかなと思ってやってみた. Electron使いたかっただけ.Electronで単純にWrapしてやるだけ.
基本的なことは下記Qiitaに全てまとまっていたので,なぞっていけば楽勝. ただし,FlashPlayerを使うためのPepperFlashPlayer.pluginの箇所は僕の環境だと下記になった.
/Users/user-name/Library/Application Support/Google/Chrome/PepperFlash/25.0.0.127/PepperFlashPlayer.plugin
なんだかんだこのへんに注意しながらやるとあっさりWrapできた. 自ドメインに置いてみたので,雑に使ってみたい人はためしてください. 気が向いたら改良していきます.
Electronも随分便利だなあ.
参考
vue-cliで生成したVue.js 2.0 なアプリケーションをherokuにデプロイする
Vue.js 2.0
最近のWebフロント界隈といえばReact + Redux一強だが,Vue.js 2.0もなかなか使いやすい. 公式が作成しているジェネレータvue-cliを用いてプロジェクトを生成すると,環境構築を考えることなく一通り試すことができる.
Vue.js 2.0はReact.jsほどガチガチのコンポーネント指向ではなく,Angular.js 1.x系のバインディングにコンポーネント指向を合わせたイメージ. vue-loaderが強力なので,html, js(es5), sassを一つのvueファイルにコンポーネントとして持つことが出来る. また,vue-routerも結構直感的なので本当にあっさりSPAを構築できる. webpack, webpack-dev-serverと合わせると爆速でページが量産されていく.
しばらくはVue 2.0で作っていきたい.
herokuへデプロイ
さて,Vue 2.0でページをサクサク作ったらどこかで公開しておきたくなる. herokuへのデプロイをしたいが,手順で若干詰まったのでメモ.
ローカルでの準備
本番用ファイルの準備
% npm run build
すると,本番用のファイルがdist以下に生成される. デフォルトだと.gitignoreによってリポジトリに乗らないようになっているので,これを削除してリポジトリに乗るようにしておく.
サーバーファイルの準備
heroku上で動作するサーバーファイル(node.js)を作っておく. 下記をserver.jsとして, dist以下に作成する.
var express = require('express'); var path = require('path'); var serveStatic = require('serve-static'); app = express(); app.use(serveStatic(__dirname)); var port = process.env.PORT || 5000; app.listen(port); console.log('server started '+ port);
ためしに
% node server.js
すると,localhost:5000で作成したページが表示されるはずだ.
package.jsonの準備
heroku用にpackage.jsonを作成する.とはいえ,元々ルートディレクトリにあったpackage.jsonをコピーして手を加えるだけだ.
% cp package.json dist/
した後,下記をコピー先のpackage.jsonに追記する.
scripts: { ... "postinstall": "npm install express", "start": "node server.js" }
ここまで準備できたら,一度gitにpushしておこう(heroku以外でも管理している場合).
herokuの準備
herokuのアプリを新規で作成しておく. 作成して,herokuのremoteを登録しておく. また,buildpacksのnodejsを指定する.
% heroku git:remote -a vue-application % heroku buildpacks:set heroku/nodejs
pushする
いよいよpushとなる. ここで重要なのは,distディレクトリのみをpushすることだ. subtreeを用いて,下記のように叩く.
% git subtree push --prefix dist heroku master
heroku openする
出来上がり.
あとがき
手順を確立してしまうと簡単だが,少しはまってしまった. このように本番ビルドの結果 + 動作用のサーバーアプリケーションのみをデプロイする方法はvue以外でも活用できそうなので,覚えておきたい.
参考
ほとんどのやり方はこちらを参考にした.ありがとうございます. https://medium.com/@sagarjauhari/quick-n-clean-way-to-deploy-vue-webpack-apps-on-heroku-b522d3904bc8#.752sfjdmv
AnsibleとPackerを使ってAWS上にWordPressを展開するDocker Imageを作成する
概要
Ansible入門として,Amazon Linuxイメージを展開したDockerコンテナ上にWordPress環境を構築するplaybookを作成する. また,ローカルでのDockerコンテナ生成からplaybook適用後のDocker Imageの生成までを,Packerを用いて一括して行うようにする.
今回はAWS上に展開することを想定して各種ファイルを作成したが,実際にWordpressをAWS上に展開する場合はAWS Marketplaceを利用した方が圧倒的に楽なのでそちらを使うべきだろう.
Docker
amazonlinuxイメージをpullしておく. テスト環境として,
% docker run -i -t -d --name aws -p 3000:80 amazonlinux
としてコンテナを生成し,ここに向けてansibleを実行する.
Ansible
下準備
playbook.ymlをローカルで作成し,上記で作成したdockerコンテナ上に流し込む. 実行前にansibleで使用するhostsファイルを作成する. Ansible 2.0以降はDocker Connectionが使用できるので,下記のようなファイルをローカルに作成する.
[docker] aws
上記をdockerHostsというファイル名で保存しておく.ansible-playbook時に使用する.
playbook
実際のplaybookを作成する. 今回は下記のようなplaybookになる. wordpress周りの各種設定はAWSのガイドラインそのままなので,適宜変更・追加が必要になる.
- hosts: all connection: docker # 初期設定 # mysql周りのパラメーター vars: mysql_user: "wordpress-user" mysql_password: "hogehogehoge" mysql_database: "wordpress_db" tasks: # 各種パッケージのインストール(vimは趣味) - name: install yum packages yum: name={{item}} state=latest with_items: - vim - httpd - mysql - mysql-devel - mysql-server - php - php-devel - php-mysql - php-mbstring - php-gd - MySQL-python27 - MySQL-python # wordpressのダウンロードと展開 - name: wordpress download get_url: url="https://wordpress.org/latest.tar.gz" dest=/wordpress-latest.tar.gz - name: unarchive wordpress command: tar -xzf /wordpress-latest.tar.gz chdir=/var/www/html # mysqld起動のためのネットワーク設定,mysqld起動 - name: network settings for mysql shell: echo "NETWORKING=yes" >/etc/sysconfig/network - name: enable mysqld service: name=mysqld state=started enabled=yes # dbとuserの作成,権限設定 - name: create mysql database mysql_db: name: "{{mysql_database}}" state: present - name: create mysql user shell: | mysql << " _EOF_" CREATE USER "{{mysql_user}}"@'localhost' IDENTIFIED BY "{{mysql_password}}"; GRANT ALL PRIVILEGES ON `{{mysql_database}}`.* TO "{{mysql_user}}"@"localhost"; FLUSH PRIVILEGES; _EOF_ # wordpress設定ファイルの作成 - name: config wordpress lineinfile: dest: /var/www/html/wordpress/wp-config.php create: yes insertafter: yes line: | <?php define('DB_NAME', '{{mysql_database}}'); define('DB_USER', '{{mysql_user}}'); define('DB_PASSWORD', '{{mysql_password}}'); define('DB_HOST', 'localhost'); define('DB_CHARSET', 'utf8'); define('DB_COLLATE', ''); define('AUTH_KEY', 'put your unique phrase here'); define('SECURE_AUTH_KEY', 'put your unique phrase here'); define('LOGGED_IN_KEY', 'put your unique phrase here'); define('NONCE_KEY', 'put your unique phrase here'); define('AUTH_SALT', 'put your unique phrase here'); define('SECURE_AUTH_SALT', 'put your unique phrase here'); define('LOGGED_IN_SALT', 'put your unique phrase here'); define('NONCE_SALT', 'put your unique phrase here'); $table_prefix = 'wp_'; define('WP_DEBUG', false); if ( !defined('ABSPATH') ) define('ABSPATH', dirname(__FILE__) . '/'); require_once(ABSPATH . 'wp-settings.php'); # apacheの設定 - name: "modify httpd.conf" shell: >- c='/etc/httpd/conf/httpd.conf' && k='<Directory "\/var\/www\/html">' && s='AllowOverride None' && r='AllowOverride All' && mv $c $c.backup && awk "/$k/{f=1} f==1&&/$s/{sub(/.+/,\"$r\"); f=0} 1" $c.backup > $c # apache userのグループ設定 - name: creat a group group: name: www state: present - name: add user to group shell: usermod -aG www apache - name: change user auth file: path=/var/www/html/ owner=apache group=www mode=2755 state=directory recurse=yes # httpd起動 - name: enable apache service: name=httpd state=started enabled=yes
playbook実行と確認
先程作成したDockerコンテナに向けて,上記playbookを実行する.
% ansible-playbook -i dockerHosts playbook.yml
無事に展開が終わったら,localhost:3000/wordpress にむけてアクセスすると,wordpressの初期設定画面にアクセスできる.
Packer
上記で,playbookを用いてDockerコンテナ上にWordPress環境が構築できるところまで確認できた. 最後に,Packerを用いてコンテナ生成から,環境構築後のDocker Image生成まで行う.
下記のようなjsonファイルを作成する(ファイル名はpacker.jsonとする).
{ "builders":[{ "type": "docker", "image": "amazonlinux", "export_path": "controller.tar", "run_command": ["-d", "-i", "-t", "--name", "aws", "-p", "3000:80", "{{.Image}}"], "pull": false }], "provisioners":[{ "type": "ansible", "playbook_file": "./playbook.yml", "extra_arguments": ["-i", "dockerHosts"] }], "post-processors": [{ "type": "docker-import", "repository": "wordpress-aws", "tag": "aws" }] }
上記のjsonファイルを用いて,次のように実行する.
% packer build packer.json
これで,Wordpress環境が構築されたDocker Imageが生成される.
さいごに
Wordpress環境を構築した状態のDocker Imageを一通り生成するところまでが確認できた. AnsibleとPackerは初体験だったが,まずは使ってみることができたので満足した. 上記で生成されたDocker Imageを,例えばECRに登録して適宜デプロイされるようにしておく,などが次のゴールになる.
間違いや,より効率的な書き方があると思われるので適宜ご指摘いただけますと幸いです.
2017 5/29 追記
ダウンロードしたwordpressを解凍する際にバージョンが固定になっていたので,最新バージョンをダウンロード・解凍するように変更しました.
tumblrAPI(tumblr.js)のoffset,before,after
tumblr.jsを使ってtumblrAPIを叩いて投稿データを取ってくるのは,認証周りでさえコケなければ難しくない. ただ,実際にAPIを叩く際には少し配慮が必要となる.
tumblrAPIのoffset,beforeとafter
tumblrAPIの仕様として,offsetは上限が1000件と決められているらしい. offset=1000以降は遡ることが出来ない.
そこで,beforeとafterというパラメータをAPIに渡してやる解決する. beforeとafterは共にtimestamp型のデータを渡して使う. beforeは○○以前,afterは○○以降のデータを返してくれる. よって,例えばアーカイブページを自前で作ろうとした場合には, beforeに現在のtimestampを渡してやって,その後追加でデータを読み込む際には,最初に受け取ったデータの中から一番古いタイムスタンプを渡してやれば良い.
注意点として,
- before, afterを使う際にはoffsetは使用できない
- before, afterは同時に併用できない
- 取得件数はデフォルトで20件となる(limitを変えて色々試してみたが,最大でも50件だった)
このように,tumblrAPIを介して投稿データを取ってくる際にはパラメータに気を配る必要がある. 調べてもあまりこの辺の話は出てこなかったので,誰かの役に立てば幸い.