一人もくもく会 α verでサービス開始しました。
請求書作成システム α verでサービス開始しました。

angular-cliでAoTによる高速化

Angular2はAoTというものがあり、動作を高速化することができる。

他の記事で色々設定が必要というものを見かけるが、angular-cliを使っている場合は元々機能があるのですぐ有効化することができる。

ng build --prod --aot

で有効化できるし、そもそも最新のangular-cliだとデフォルトで有効化されているため –aot オプションは不要。

Angular2.2から2.4にしてangular-cliも最新にしてみたところ、元々ビルドされたmain.jsが90MBほどあったものが800kBほどに最適化された。 スマホで10秒近くかかっていた初期化も改善された。

古いバージョンを使っている人は早めに最新バージョンに変更した方が良い。

ただ、色々と別のパッケージを使っていると思うが、それらはバージョンが合わないと動作しなかったりする。 自分もng2-tag-input0.8.5だと動かないので0.8.0にしたら動作したし、 ネット上の情報だと他にも同様のエラーがでるパッケージがある。 色々とバージョンの調整は必要。

Linux Mint 18でmailtodisk

Linux Mint 18でxamppに入っているmailtodiskと同じことができるようにした。 多分Ubuntu16あたりでも同じだと思う。

詳しくは

php - Use of mailtodisk / mailoutput in XAMPP for Linux - Stack Overflow

にかかれていることそのままなのだが、例えば下記のようなファイルを作成する。

/usr/local/bin/mailtodisk (実行属性を忘れないように)

#!/usr/bin/php
<?php
$input = file_get_contents('php://stdin');
$dir = '/home/yourname/Documents/mailoutput/';
$filename = $dir . 'mail-' . gmdate('Ymd-Hi-s') . '.txt';
$retry = 0;
while(is_file($filename))
{
    $filename = $dir . 'mail-' . gmdate('Ymd-Hi-s') . '-' . ++$retry . '.txt';
}
file_put_contents($filename, $input);

そして上記の$dirで保存フォルダを指定。 保存フォルダに書き込み属性を忘れずに。

あとはphp.iniのsendmail_pathに上記スクリプトを指定すれば良い。 apache用とcli用の2つがあった。

sendmail_path = /usr/local/bin/mailtodisk

さらに下記のようなものを以前作ったので、 localhost以下に適当に配置してブックマークしておけば簡単にメールを見たり削除したりできる。

GitHub - dala00/MailoutputViewer

Angular2アプリケーションを通常のサーバーにデプロイ

Angular2アプリケーションをangular-cliでビルドすると公開用のdistフォルダが作成されるので、 基本的にはこれをサーバーにアップすればそのまま動作する。

ただ、jsファイルはハッシュ値がついてファイル名が変わってしまうため、 多少気をつけないといけない。

簡単にデプロイするための方法として下記のような方法があげられる。

distフォルダもignoreせずバージョン管理してしまう

特に支障がなければdistフォルダもcommitしてしまえば良いと思う。

ソースに変更がなければ何度ビルドしてもハッシュ値は変わらない気がするので、 バージョンアップの際だけビルドしてそのままコミットで問題ないと思う。 あとは本番サーバーでpullするだけ。

デプロイ用のプログラムを作成する

何らかの理由でdistフォルダ以外は本番にアップしたくない場合、 上記方法は使えないので自動デプロイ用のプログラムを用意する必要がある。

具体的には下記のような処理のプログラム。

  1. 準備用フォルダを作成
  2. 準備用フォルダに最新のdist内ファイルを全てアップロード
  3. 本番フォルダと準備用フォルダを入れ替え

一人もくもく会 用にぱぱっと適当に作成したプログラムのサンプル。

Deploying script with sftp for Angular. 'npm run deploy' or 'npm run deploy rollback' · GitHub

必要に応じて準備用フォルダを事前に削除したり、古い本番フォルダを残してバージョン管理するなり削除するなりが必要かと思われる。

.htaccess

関係ないので補足になるが、ルーティングを使っている場合、トップページ以外からアクセスが来た場合もそのURLのページを表示しなければならないので、 下記のような.htaccessの設定が必要となる。

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]

Angular2とCakePHP3を共存させる

AngularとCakePHPを共存、つまり同じホスト名で動作させたい場合。 例えば下記のように隣同士で並んでいるとする。 Angularはビルドしたdistフォルダのみの公開でその他のソースはアップなどはしない場合。

  • app
  • dist

公開フォルダはAngular側のdistフォルダにするが、 AngularもCakePHPもルーティングが必要になるので、distフォルダ内の.htaccessで両方動作するように設定を行う。

CakePHP側は、全てapiというprefix内にプログラムを入れることで振り分けが容易となる。

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} ^/api/
RewriteRule ^ index.php [L]
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]

index.phpCakePHPのwebrootのもののパス部分の記述だけを修正。下記の2箇所。

require dirname(__DIR__) . '/app/vendor/autoload.php';
$server = new Server(new Application(dirname(__DIR__) . '/app/config'));

index.php.htaccessはAngularの開発時のsrcフォルダに入れておき、 angular-cli.jsonのapps.assetsの設定に含めておけば良い。

こういった方法であればホスト名も同じにできるので、SSLを2つ分とったり、などという手間をかける必要がなくなる。

一人もくもく会というサービスを作成

一人もくもく会というサービスを作成して公開した。

mokumoku.alphabrend.com

概要

世の中誰でも夜暇な時に適当にプログラミングをあれこれ試す時があると思うが、 そういった時のログを投稿できるサービス。

Qiitaやブログのようにきっちりプログラムや記事を完成させなくても、 適当にどんどんその日毎のログを残すためのもの。

フレームワーク

  • Angular
  • CakePHP3

デザインはとりあえずAngular MaterialやMaterial Design Liteをごちゃごちゃに使っている。 できればAngular Materialのみでやりたかったが、こちらは現在betaで開発中のため色々足りない。 他のあちらこちらから色々使えるパーツを探してきて使う必要がある。

現状

とりあえず必要最低限以下の機能を作成しただけなので、αバージョン。 誰でもすぐGithubアカウントかTwitterアカウントでログインできるので、 気が向いた方は使っていただける状態。 0円でSSLもつけているので。

今後

作成は進めていくが、せっかくなのでその開発ログも実際にサービス上に残していく予定。 誰にも利用されなくてもデータが増えていくというのは一石二鳥だ。

その他補足色々

マークダウン

投稿はmarkdownで可能。 Github色のシンタックスハイライトも入れてある。

SPA

Angularを使っているのでSPAとなっている。 面倒なのでサーバーサイドレンダリングはしていない。 とりあえずGoogleのクローラに全部お任せ。 きっとそのうちAngular自体がデフォルトでUniversal対応してくれるだろう。

Google Analytics

SPA用にAutotrackというのがあるのでそれを使った。動いているかは不明。

ソーシャルシェア、ブックマーク

投稿データにシェア、ブックマークボタンをいくつか入れてみた。 SPA用に調整してあるので多分動くと思うがまだ記事が0なので本番では未確認。 うまく動かなかったら投稿しつつ修正していく。

タグ

タグ機能があり自由にタグを追加できるので、 イベントタグを作ってみんなで参加、ということも可能。 これでオフラインもくもく会を流行らせたい。

ソース管理

非公開なのでBitBucket。CakePHP3のCIはCodeship。 Angularは元々テストでエラーが出まくってしまうのでエラーを取り切ってから対応予定。

不具合

いくつかある。早めに必要な機能などもある。掲載しつつ修正していく。

デプロイ

distフォルダ毎サーバーにアップすれば良いのだが、 それだけでも毎回面倒なのでデプロイ用のプログラムを作る必要がある。

ただ、各ファイルのハッシュ値はソースを変更しなければ毎回同じっぽいので、 dist毎pushしてしまえばサーバー側はpullだけでもいいかもしれない。

ちなみに、distディレクトリにCakePHP用のindex.phpが入っていて、.htaccessCakePHP側とAngular側を振り分けている。 またこの詳細は別記事に書く。

CodeIgniterのコーディング規約が独自すぎる

CodeIgniterのコーディングスタイルが独自路線過ぎて唖然とした。 PSR等この世に存在しない、と言わんばかりのドキュメントページ。

PHP Style Guide — CodeIgniter 3.1.3 documentation

クラス名、メソッド名、変数名

<?php
class Super_class
function get_file_properties()
$group_id

当然、スネークケース。

TRUE, FALSE, and NULL

大文字で。

論理演算子

下記がCORRECT

<?php
if ($foo OR $bar)
if ($foo && $bar) // recommended
if ( ! $foo)
if ( ! is_array($foo))

このあたりは2度見した。ORもそうだが、否定演算子を離すのとかはどうなんだろう。 || は 11 と見間違える可能性があるから、と書いてあるがそんなことあるのだろうか。 スペース区切りになってるからスペース省略して書いてる変な人以外は見間違えないと思うのだが。

否定演算子演算子だから他と一緒でスペースを開けろ、という意味なのだろうか? このへんはちょっと思想が深すぎる気がする。

インデント

当然ハードタブ。 各エディタで見栄えを調整できるしファイルサイズも抑えられるからだそうだ。 中括弧も全部オールマン形式。

テンプレート内変数

<?= $var ?>

これはだめ。全部下記で。

<?php echo $var; ?>

理由は、サーバーによってエラーが出る可能性があるからだそうだ。 何時の時代の話なのだろうか。 (5.4なら大丈夫だよ! と書かれている)

Compatibility(互換性)

つまりは、恐らくほとんどのことがこれをベースに決められているためのように感じた。 PHP 5.3.7でも動くようにしろ、と書かれている。

そんなバージョンを使っているサーバーなどまだあるのだろうか。 まあ、あるのかもしれないが、それのほうが問題のような気がする。 もちろんCOBOLがまだ生き残っているように、やむを得ない理由はあるのかもしれないが。

ハード多分云々もパフォーマンスの低いサーバーがまだ生き残っているためかもしれない。 ここの説明で多少不思議は解消された。 つまり時代遅れなのではなく、わざと時代から取り残される道を選んでいる。

とはいえそれで疑問が全て解消されるわけではない。 多分、意図的かどうかはわからないが、マニュアルも思想も古いまま置き去りになっているようには感じる。

CodeIgniter4

クラス名関連はキャメルケースになったり、多少PSR-2に似ている部分ができたと書かれている。

Angular2でコンポーネントのユニットテストを書いてみた

Angularのコンポーネントユニットテストを書いてみた。 TestBedを使用しているパターン。

angular-cliでファイルを作成しつつ、 アプリケーションをある程度書いてng testしてみると大量にエラーが出るので、 それらをいくつかエラーが出ないように修正してみた。

エラーが出る原因

基本的には、htmlで使用されているコンポーネントや、 ts内で使用されているサービスなどの依存関係が満たされていないため。

アプリケーション実行時はmoduleの設定などで満たされているが、 テストは各テストで別途設定してあげなければならない。

テストは別途設定にすることで、各サービスなどはmockを使用できるようになっている。

テスト例

ユーザー情報を取得して表示するだけのページのコンポーネント例。 コンポーネント側ではActivatedRouteからidを取得し、 UserServiceでそのidからhttpでユーザー情報であるUserを取得し、 Userをコンポーネント上で表示するだけの例。

/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

import { ActivatedRouteStub } from '../../../../testing/router-stubs';
import { UserServiceStub } from '../../../../testing/user-service-stub';
import { UserDetailComponent } from './user-detail.component';
import { UserService } from '../../../services/user.service';
import { User } from '../../../models/user';

describe('UserDetailComponent', () => {
  let component: UserDetailComponent;
  let fixture: ComponentFixture<UserDetailComponent>;
  let activatedRoute = new ActivatedRouteStub();
  let userService = new UserServiceStub();
  let user = new User();

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ UserDetailComponent ],
      providers: [
        {provide: ActivatedRoute, useValue: activatedRoute},
        {provide: UserService, useValue: userService}
      ],
    })
    .compileComponents();

    activatedRoute.testParams = {id: 1};
    userService.user = user;
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(UserDetailComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('user set', () => {
    expect(component.user && component.user == user).toBeTruthy();
  });
});

declarations

html上に表示するコンポーネントはここで設定。 他のコンポーネントを使っている場合はここに追記。

providers

サービスなどの設定。provideで実際のクラスを指定し、useValueでモックを指定できる。

モック

router-stubs.tsは下記で書かれたものを改造したりして使用。

Testing - ts - GUIDE

Observableを使用しているのでクラスにまとめた方が使いまわしやすい、ということ。 providersではそれらのモックを使用し、compileComponentでコンポーネントが構築された後、

    activatedRoute.testParams = {id: 1};
    userService.user = user;

で返すべき値をセットしている。 上記はgetメソッドになっているので、セットされるとコールバックが起動されて処理が進んでいく。 あとは自由にitで確認していくだけ。

適当にぱぱっとエラーを消して処理を追加しただけなので全体的に正しいかどうかは不明。