FRESH を卒業しました 🎉

はじめに

事実上, 2018 年 11 月 12 日 (月), 肩書き上, 2018 年 11 月 15 (木) をもって FRESH を卒業しました. 2016 年 11 月 1 日から, FRESH に所属していたので, ちょうど 2 年ぐらいお世話になりました.

FRESH にくるまでの経緯

わたしは, 2015 年 4 月に新卒の Web エンジニアとして, ドワンゴに入社しました. ところが, (会社全体でみればわかりませんが, 少なくともわたしの配属された部署は) 仕事の与え方が年功序列なのにたえきれず, 1 年 6 ヶ月で退職しました. 以下は, その具体例です.

  • Web エンジニアとして採用されたのに, 配属されてすぐぐらいに, テストエンジニアのような仕事をやらされた (約 2 ヶ月間, 仕事でまったくコードを書かせてもらえなかった …)
  • 動画が再生できるかの検証をひたすらやらされた (コードを書かせてもらえなかった …)
  • あげくの果てに, 社内ニートにさせられた

(* ドワンゴが悪い環境という意味で書いてはいません. 楽しく働いている同期もたくさんいます. ただ, 事実として, そういうあつかいを実際にうけました)

人によっては, 社内ニート最高じゃん ! … と思われるかもしれませんが, Web エンジニアとして, レベルアップをするために, いろんなことを犠牲にして地元を離れて, わざわざ上京してきたわたしにとっては苦痛以外なにものでもありませんでした.

そんななか, Wantedly 経由で FRESH にきませんか ? というスカウトメールを受けて, 面接をして, (未だに何を評価されたのかは謎なのですが) 面接官であった, @ahomu さんがわたしを採用してくれました. なんのスキルもない, なんの実績もないわたしを …

FRESH で関わった仕事

FRESH でわたしが関わった仕事で, 比較的大きな仕事は …

  • Service Worker の導入と, Firebase Cloud Messaging (FCM) を利用した Web Push の実装
  • 配信面 を, Flummox から, Redux + redux-saga でリプレース
  • FIL の日を活用した, FRESH EQUALIZER の実装や, Go と Slack を利用したテスト業務自動化

以上の, 3 つかなと思います !

Service Worker の導入と, FCM を利用した Web Push の実装

わたしが FRESH にきてから, はじめて成し遂げた大きな仕事でした. もし, この仕事がなければ, わたしは FRESH をクビになっていたでしょう w. ドワンゴ時代と違い, 多少スキルが不足していても, 自分からやりたい ! と手をあげれば, 任せてくれる … そんな環境だったからこそチャレンジすることができました. この仕事では, @1000ch さんや, @soushi_nozawa さんのサポートがあってこそ成し遂げれた仕事でもありました(FRESH! における Web プッシュ通知機能 〜実装編〜). さらに, この仕事が, 2018 年 6 月 23 日 (土) に出版された, WEB+DB PRESS Vol.105 の執筆にもつながりました.

配信面を Flummox から Redux + redux-saga へリプレース

FRESH で成し遂げた仕事で, 最大規模の仕事でした … ほんと大変でした w Flux フレームワークである, Flummox はすでに開発がとまってしまい, このままだと React のバージョンアップなどに追従できなくなるというのがリプレースの理由でした. 約 9 ヶ月を要したこの仕事も, 最初の方こそ, わたし 1 人で進めていましたが, リリースが近くにつれ, フロントエンドのメンバーはもちろん, インフラ, テスター, ディレクターの多くの方々のサポートをうけて, 未だ大きな事故を起こすことなく, リリースすることができました.

FIL の日を活用した, FRESH EQUALIZER の実装や, Go と Slack を利用したテスト業務自動化

FIL の日とは, 2017 年 9 月から FRESH でスタートした制度で, 簡単に説明しますと, 隔週 (2 週間に 1 度の) 金曜日に, 通常業務はせず, FRESH に関わることであれば何をやってもいいという制度でした. 3 – 5 年後ぐらいを見据えた研究的なことをする人もいれば, 興味ある技術を FRESH に使ってみたり, 負債の解消に取り組んだり … みんな様々なことに取り組んでいました.

FRESH EQUALIZER

これは, わたしが FIL で取り組んだ最初のプロダクトです. Web Music を広めていきたい, また, オーディオ技術を業務に活用したい, また, 動画ストリーミングに対して Web Audio API が適用できるのか ? ということを検証したいことがモチベーションでした. 詳細は, FIL の日と FRESH! EQUALIZER を参考にしてみてください.

Go と Slack を利用したテスト業務自動化

Go を業務で使いたい ! それだけがモチベーションで手をあげました w 詳細は, 業務内容の詳細に関わることなので公にはできませんが, FIL があったからこそ, 趣味でしか使ったことがなかった Go を, 業務で使うことができました.

今後について

FRESH にきて 2 年が経過したのを機に, エンジニアとしてのキャリアを見直し, さらなる成長を求めて異動を決断しました. 新しい部署では, @ahomu さんを筆頭に, @hiloki さんや @wadackel さんなど, 日本トップクラスのフロントエンドエンジニアとともに仕事をすることになりました. すでに異動はしているので, そのレベルの高さを怖いぐらいに実感しています. 一方で, わたしのとがった領域であるメディア技術 (オーディオ, 動画ストリーミングなど) で早々に貢献したいと思っています. 異動して数日ですが, この決断は間違いでないと思います !

Special Thanks

2016 年 11 月 1 日に FRESH に配属され (入社して), なんのスキルもなく, なにもできないわたしを当時のフロントエンドのリーダーであった @pocotan001 さんは技術的な面をサポートしてくださり (レビューでいただいた, 「ピクセルパーフェクトより整合性を」というアドバイスは今も染み付いて離れないぐらいです), また, @tommy___san さんは技術以外での仕事の進め方をサポートしてくださり, @sutiwo_ さんにはわからないことをていねいにサポートしてくださりました. そして, 日本屈指のアクセシビリティおじさんである @masuP9 さんによって, アクセシビリティという今まで意識してこなかったことを強く意識するようになりました.

個人的は, @1000ch さんが FRESH のサポートに加入したことが 1 つの転機となりました.

また, hls.js の Contributor として 1 まわり成長する機会となった, @thmatuza さんのサポートをうけての動画プレイヤーの低遅延導入.

仕事では直接関わることはありませんでしたが, 10 歳近く下なのに, スーパー iOS エンジニアの @ra1028fe5 さんや, チンチラ愛のた ◯ はしさんには, いろんな意味でお世話になりました.

そして, FRESH のテックリードとして, FRESH のエンジニアを牽引してくださった @stormcat24 さん … (最後の最後には, 転職か異動かで迷っていたわたしをサポートしてくださりました. 餞別の品である, オジュウチョウサンをお渡しできてよかったです w).

また, エンジニア以外の方でも, FRESH の偉大なるボスであった @manualog さんをはじめ, FRESH のビジネス面を牽引してくださった方々にもたくさんお世話になりました.

この 2 年間, 仕事において, わたしに関わってくれたすべての人に感謝の気持ちでいっぱいです !

~ 輝く季節が 永遠に変わるまで ~ 2018 年 11 月 15 日 FRESH 卒業

Angular でモデル駆動型フォームを実装してみた

Overview

Angular でフォームを実装してみた では, より手軽に実装可能な, テンプレート駆動型のフォームについて記載しました. テンプレート駆動型のフォームでは, 検証ルールをテンプレートに記述していました.

検証ルールはテンプレートに記述するだけではなく, コンポーネント側に記述することも可能です. これが, モデル駆動型 (Reactive 駆動型) のフォームです. テンプレート駆動型のフォームと比較すると, 冗長なコードになってしまいますが, より柔軟に, 複雑な検証ルールを実装できます.

ReactiveFormsModule

モデル駆動型のフォームを実装するには, FormsModule の代わりに, ReactiveFormsModule をインポートする必要があります.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

そして, テンプレートとコンポーネントは以下のようになります.

<form [formGroup]="myForm" (ngSubmit)="show()">
  <div>
    <label>メールアドレス: <input type="email" name="mail" [formControl]="mail" /></label>
    <span *ngIf="mail.errors?.required">メールアドレスは必須です.</span>
    <span *ngIf="mail.errors?.email">メールアドレスを正しい形式で入力してください.</span>
  </div>
  <div>
    <label>パスワード: <input type="password" name="password" [formControl]="password" /></label>
    <span *ngIf="password.errors?.required">パスワードは必須です.</span>
    <span *ngIf="password.errors?.minlength">パスワードは 6 文字以上で入力してください.</span>
  </div>
  <div>
    <label>名前: <input type="text" name="name" [formControl]="name" /></label>
    <span *ngIf="name.errors?.required">名前は必須です.</span>
    <span *ngIf="name.errors?.minlength">名前は 3 文字以上で入力してください.</span>
    <span *ngIf="name.errors?.maxlength">名前は 10 文字以内で入力してください.</span>
  </div>
  <div>
    <label>備考: <textarea name="memo" [formControl]="memo"></textarea></label>
    <span *ngIf="memo.errors?.maxlength">備考は 10 文字以内で入力してください.</span>
  </div>
  <div>
    <button type="submit" [disabled]="myForm.invalid">送信</button> 
  </div>
</form>
import { Component } from '@angular/core';
import {
  FormGroup,
  FormControl,
  FormBuilder,
  Validators
} from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  private title = 'app';

  private mail     = new FormControl('', [Validators.required, Validators.email]);
  private password = new FormControl('', [Validators.required, Validators.minLength(6)]);
  private name     = new FormControl('', [Validators.required, Validators.minLength(3), Validators.maxLength(10)]);
  private memo     = new FormControl('', [Validators.required, Validators.maxLength(6)]);

  private myForm = this.builder.group({
    mail     : this.mail,
    password : this.password,
    name     : this.name,
    memo     : this.memo
  });

  constructor(private builder: FormBuilder) {
  }

  show() {
    console.log(this.myForm.value);
  }
}

モデル駆動型のフォームにおいて, 重要となる, FromControl, FormGroup について解説します.

FormControl

FormControl は, コンポーネント内で, そのインスタンスを生成して利用します.

new FormControl(value [, validators])

第 1 引数は, フォームの初期値, 第 2 引数は, (必要であれば) 検証ルールを配列で指定します. 検証ルールには, Validators クラスを利用することができます.

  private mail     = new FormControl('', [Validators.required, Validators.email]);
  private password = new FormControl('', [Validators.required, Validators.minLength(6)]);
  private name     = new FormControl('', [Validators.required, Validators.minLength(3), Validators.maxLength(10)]);
  private memo     = new FormControl('', [Validators.required, Validators.maxLength(6)]);

FormGroup

FormGroup は, FormControl インスタンスを束ねる役割をもっています. これによって, フォーム全体でまとめて, エラーが発生していないかを検証することが可能になります.

FormGroup を利用するには, コンポーネントのコンストラクタで, FormBuilder 型の引数をうけとるようにします (ちなみに, これは依存性注入 (DI) と呼ばれるデザインパターンの 1 種です).

  constructor(private builder: FormBuilder) {
  }

FormControl インスタンスを束ねるには, FormBuilder インスタンスの group メソッドを利用します.

  private myForm = this.builder.group({
    mail     : this.mail,
    password : this.password,
    name     : this.name,
    memo     : this.memo
  });

テンプレートとのひもづけ

コンポーネント側で必要な実装ができれば, あとは, コンポーネントとテンプレートをひもづけるだけです. そのためには, formGroup ディレクティブと formControl ディレクティブを利用します.

<form [formGroup]="myForm" (ngSubmit)="show()">
  <div>
    <label>メールアドレス: <input type="email" name="mail" [formControl]="mail" /></label>
    <span *ngIf="mail.errors?.required">メールアドレスは必須です.</span>
    <span *ngIf="mail.errors?.email">メールアドレスを正しい形式で入力してください.</span>
  </div>
    <!-- ... -->
</form>

Angular でファイルアップローダーを実装してみた

1. Overview

前回に引き続き, Angular でのフォーム実装を学びます. 今回は, ファイルアップローダーの実装をとおして, Angular における, HTTP クラアントの実装と, 非同期処理の実装について学びます.

2. HttpClientModule / HttpClient

Angular で HTTP クライアントを実装するには, HttpClientModule / HttpClient を利用します.

src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

src/app/app.component.ts

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  private title = 'File Uploader by Angular';

  constructor(http: HttpClient) {
  }
}

HttpModule / Http を利用しているサンプルコードなどもありますが, それらは非推奨なので注意してください.

3. RxJS

RxJS (Reactive Extensions Library for JavaScript) とは, 非同期およびコールバックベースのコードを, 関数的, かつ, リアクティブなスタイルで作成するためのライブラリです. Angular では, 非同期処理にこの RxJS を内部的に利用しています.

3. Implement

それでは, 実際に実装をしてみます. まず, 必要となるファイルアップロードサーバーを実装する必要があります. 言語はなんでも構いません. 今回は, Go で実装してみました.

uploader.go

package main

import (
    "encoding/json"
    "io"
    "net/http"
)

func uploadHandler(w http.ResponseWriter, r *http.Request) {
    reader, err := r.MultipartReader()

    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    h := w.Header()

    h.Set("Access-Control-Allow-Origin", "*")
    h.Set("Content-Type", "application/json")

    for {
        part, err := reader.NextPart()

        if err == io.EOF {
            break
        }

        if part.FileName() == "" {
            continue
        }

        res, err := json.Marshal(part)

        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        w.Write(res)
    }
}

func main() {
    http.HandleFunc("/upload", uploadHandler)
    http.ListenAndServe(":8080", nil)
}

Angular の実装です. まずは, テンプレートの実装です.

src/app/app.component.html

<form>
  <input #uploader type="file" accept="image/*" (change)="upload(uploader.files)" />
<form>

#uploader はテンプレート参照変数です. change は, イベントバインディングで, ファイルダイアログでファイルが選択されたときに発火します.

次に, コンポーネントの実装です.

src/app/app.component.html

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  private title = 'File Uploader by Angular';

  constructor(private http: HttpClient) {
  }

  upload(files: Array) {
    if (files.length <= 0) {
      return;
    }

    const file = files[0];
    const formData = new FormData();

    formData.append('file', file, file.name);

    this.http.post('http://localhost:8080/upload', formData)
      .subscribe(
        data => console.log(data),
        error => console.log(error)
      );
  }
}

Angular の処理が関連しているのは, HttpClient#post メソッドの部分です. HttpClient#post メソッドは, 第 1 引数にリクエスト先の URL を, 第 2 引数に POST するデータを指定します. Http#post メソッドは, RxJS の Observable を返します. Observable からデータを取得するには, Observable#subscribe メソッドを利用します. Observable#subscribe メソッドは, Observer インスタンスを引数にとります. Observer は onNext / onError / onCompleted をもっており, Observable によって呼びだされ, それぞれ, ストリームの値, エラー, 完了を通知します (上記の, サンプルでは, onCompleted は利用していませんが …).

以上で実装は完了です.

動作確認するには, まず, ファイルアップロードサーバーをバックグランドで起動しておきます.

$ go run uploader.go &

あとは, npm start を実行して, ローカルサーバーを起動します.

$ npm start

ファイルアップロードをして, ストリームの値が取得できれば成功です.

Angular でフォーム実装してみた

1. Overview

フォームの実装をとおして, Angular (Angular 2 〜) の基礎 (の一部) を学んでみました. その備忘録です.

2. Syntax highlighting of TypeScript by Vim

と … その前に, 私はエディタに Vim を利用しています. Angular では, アプリケーションコードを TypeScript で記述する (Angular 自体も TypeScript で実装されています) ので, TypeScript のシンタックスハイライトを導入します.

プラグインマネージャーは Vundle を利用します.

まずは, 以下のリポジトリを clone します.

$ git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim

.vimrc に以下の記述を追加します.

filetype off

set rtp+=~/.vim/bundle/Vundle.vim

call vundle#begin()

Plugin 'gmarik/Vundle.vim'
Plugin 'leafgarland/typescript-vim'

call vundle#end()

filetype plugin on

 

そして, 以下のコマンドを実行します.

vim +PluginInstall +qall

3. Angular CLI

Angular は, クライアントサイドを (あえて) MVC に分類すると, すべてを担う, フルスタックフレームワークです (ちなみに, React は View のみとシンプル). そのため, (個人的主観ではありますが) ファイル構成が複雑で, 環境構築にもコストを要します. アプリケーションを実装するたびにこれらの作業をこなすのは現実的ではありません.

Angular CLI はこの問題を簡単に解決してくれます. Angular CLI を利用することで, コマンド 1 つで, 最低限必要なファイルの作成や環境構, つまり, アプリケーションの雛形生成を自動でおこなってくれます. Amngular CLI を利用するには, npm でインストールします.

$ npm install -g @angular/cli

アプリケーションの雛形を生成するには, ng new を実行します. 今回は, アプリケーション名を form とします.

$ ng new form
$ cd form

 

アプリケーション名でディレクトリが生成されるので, そのディレクトリに移動し, ls でファイルを確認すると, 多くのファイルやディレクトリが生成されていることがわかります.

% ls -A
.editorconfig     README.md         e2e               package-lock.json src               tslint.json
.gitignore        angular.json      node_modules      package.json      tsconfig.json

これは便利ですね.

3. Implement Form

準備ができたのでフォームを実装します. まずは, src/app/app.module.ts に FormsModule を追加します.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

次に, src/app/app.component.ts の AppComponent に, プロパティとメソッドを定義します.

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  private title = 'Form by Angular';
  private user = {
    mail     : 'xxx@gmail.com',
    password : '',
    name     : 'rilakkuma',
    memo     : ''
  };

  show() {
    Object.values(this.user).forEach(value => alert(value));
  }
}

そして, テンプレートである, app.component.html を以下のように記述します.

<form #myForm="ngForm" (ngSubmit)="show()" novalidate>
   <div>
     <label>メールアドレス : <input type="email" name="mail" [(ngModel)]="user.mail" #mail="ngModel" required email /></label>
     <span *ngIf="mail.errors?.required">メールアドレスは必須です</span>
     <span *ngIf="mail.errors?.email">メールアドレスを正しい形式で入力してください</span>
   </div>
   <div>
     <label>パスワード : <input type="password" name="password" [(ngModel)]="user.password" #password="ngModel" required minlength="6"></label>
     <span *ngIf="password.errors?.required">パスワードは必須です</span>
     <span *ngIf="password.errors?.minlength">パスワードは 6 文字以上で入力してください</span>
   </div>
   <div>
     <label>名前 : <input type="text" name="name" [(ngModel)]="user.name" #name="ngModel" required minlength="3" maxlength="10" /></label>
     <span *ngIf="name.errors?.required">名前は必須です</span>
     <span *ngIf="name.errors?.minlength">名前は 3 文字以上で入力してください</span>
     <span *ngIf="name.errors?.maxlength">名前は 18 文字以上で入力してください</span>
   </div>
   <div>
     <label>備考 : <textarea name="memo" [(ngModel)]="user.memo" #memo="ngModel" maxlength="10"></textarea></label>
     <span *ngIf="name.errors?.maxlength">備考は 10 文字以内で入力してください</span>
   </div>
   <div>
     <button type="submit" [disabled]="myForm.invalid">送信</button>
   </div>
</form>

詳細を解説していきます.

まず, form タグには, それぞれ ngFrom ディレクティブ, ngSubmit イベントバインディング, novalidate 属性 (ブラウザによるネイティブの検証を無効にする属性です. Angular 4 〜 では, 自動で付与してくれるので不要です) を指定しています.

<form #myForm="ngForm" (ngSubmit)="show()" novalidate>

ngForm ディレクティブは, submit 時に, フォーム全体の検証をするために利用します.

<button type="submit" [disabled]="myForm.invalid">送信</button>

API が変わっているだけで, AngularJS での, ng-form, ng-submit と大差ないことに気づかれたでしょうか … ?

入力フォーム (email, password, text, textarea) です.

<label>メールアドレス : <input type="email" name="mail" [(ngModel)]="user.mail" #mail="ngModel" required email /></label>

Angular でのフォーム検証を有効にするためは, 双方向バインディング必要となるので, ngModel ディレクティブと要素を識別するための, name 属性が必須になります.

[(ngModel)]="user.mail"

では, AppComponent#user プロパティと双方向バインディングしています. また,

#mail="ngModel"

は, テンプレート参照変数で, ngModel ディレクティブを代入することで, フォーム要素の状態にアクセスできるようにします.

required, email, minlength, maxlength などは, 必要に応じて指定します.

そして, 不正な値であった場合のエラー表示に活躍するのが, *ngIf ディレクティブです (AngularJS での ng-if ディレクティブ).

<span *ngIf="mail.errors?.required">メールアドレスは必須です</span>

*ngIf ディレクティブは, その値が true であった場合のみ, 指定した要素を DOM に追加します. *ngIf ディレクティブと, フォームの検証の成否を組み合わせることで, 簡単にエラーメッセージの表示を実装することが可能です.

フォームの検証の成否の構文を一般化すると,

入力要素名 (テンプレート参照変数名).errors?.検証型

となります. ? は, Angular 2 〜 で利用可能な, Safe Navigation Operator で, errors が null や undefined であってもエラーを発生させることがありません (つまり, 安全にプロパティアクセス可能になります). 検証型には, フォームに指定した, required, email, minlength, maxlength などを指定します.

あとは, フォームに不正な値が入っていれば, submit ボタンが disabled となるように実装します.

<button type="submit" [disabled]="myForm.invalid">送信</button>

[disabled] は, プロパティバインティングです. そこに, (フォームを参照するテンプレート変数).invalid とすることで, それが実装できます. ここでは, myForm が, フォームを参照するテンプレート変数となるので, myForm.invalid と指定します.

最後に,

$ npm start

を実行することで, ng serve が実行され, 簡易サーバーが起動するので, localhost:4200 にアクセスして, フォームが機能していれば完成です !

Web Audio Library XSound 2.4.0 / 2.4.1 Released

1. Overview

久々の Web Audio API ネタです. XSound 2.4.0 からノイズの生成に対応しました. 2.4.0 ではホワイトノイズ (白色雑音), 2.4.1 ではピンクノイズの生成が可能になりました.

2. What is Noise ?

そもそもノイズ (雑音) とは何か ? から解説します. 世の中に存在する音のほとんどは周期的な波形をしており, 基本周波数をもつ sin 波とその整数倍の周波数をもつ sin 波 (倍音) の合成によって構成されます (それを周波数視点で分解するのがフーリエ変換です). しかしながら, 非周期的な波形をもつ音も存在しており, その典型例がノイズと呼ばれる音になります. ノイズにはいくつか種類がありますが, 有名でよく利用されるホワイトノイズとピンクノイズの特徴と, それを Web Audio API で生成するための実装を紹介します.

3. White Noise

ホワイトノイズ (白色雑音) の命名の由来は, そのスペクトルが白色のスペクトルと同じ (どの帯域でも同じ振幅をとる) であることから名づけられました. ホワイトノイズの実装はとても簡単です. なぜなら, 乱数を生成するだけだからです (ただし, 振幅が -1 〜 1 に収まるように値を調整する必要はあります). JavaScript の Math.random メソッドは 0 以上 1 未満の値を返すので, -0.5 したあと, 2 倍にすることで, -1 〜 1 の値に収まるようにしています.

// processor は ScriptProcessorNode のインスタンス
processor.onaudioprocess = event => {
    const outputLs = event.outputBuffer.getChannelData(0);
    const outputRs = event.outputBuffer.getChannelData(1);

    // bufferSize は ScriptProcessorNode のバッファサイズ
    for (let i = 0; i < bufferSize; i++) {
        outputLs[i] = 2 * (Math.random() - 0.5);
        outputRs[i] = 2 * (Math.random() - 0.5);
    }
}

4. Pink Noise

ピンクノイズの命名の由来は, そのスペクトルがピンク色のスペクトルと同じ (高周波数ほど振幅が減衰する) であることから名づけられました. ピンクノイズの実装は少し複雑 (すみませんが, 私も完全に理解できていません …) なので, 実装のみを紹介します.

// processor は ScriptProcessorNode のインスタンス
processor.onaudioprocess = event => {
    const outputLs = event.outputBuffer.getChannelData(0);
    const outputRs = event.outputBuffer.getChannelData(1);

    let b0 = 0;
    let b1 = 0;
    let b2 = 0;
    let b3 = 0;
    let b4 = 0;
    let b5 = 0;
    let b6 = 0;

    // bufferSize は ScriptProcessorNode のバッファサイズ
    for (let i = 0; i < bufferSize; i++) {
        const white = (Math.random() * 2) - 1;

        b0 = (0.99886 * b0) + (white * 0.0555179);
        b1 = (0.99332 * b1) + (white * 0.0750759);
        b2 = (0.96900 * b2) + (white * 0.1538520);
        b3 = (0.86650 * b3) + (white * 0.3104856);
        b4 = (0.55000 * b4) + (white * 0.5329522);
        b5 = (-0.7616 * b5) - (white * 0.0168980);

        outputLs[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + (white * 0.5362);
        outputRs[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + (white * 0.5362);

        outputLs[i] *= 0.11;
        outputRs[i] *= 0.11;

        b6 = white * 0.115926;
    }
}

う〜ん … 原理がよくわかりません w. 実は, ホワイトノイズに適切な Low-Pass Filter をかけることでも生成できるので, 原理がわからないと使いたくない場合は, その実装でもいいかもしれません.

5. References

Web Components <x-piano> v2.0.0 をリリースしました

1. Overview

Web Components である, <x-piano> v2.0.0 をリリースしました. v2.0.0 では, 以下の API で構成されています.

  • Shadow DOM v1
  • Custom Elements v1
  • ESModules

* Web Components を構成する API には HTML Templates も含まれますが, <x-piano> では利用していないので, 割愛します.

ちなみに, v1.x.x までは以下の API で構成されていました.

  • Shadow DOM v0
  • Custom Elements v0
  • HTML Imports

詳細な仕様は, Web Fundamentals などに記載されていますので, <x-piano> v2.0.0 のコードを例に, それぞれの API がどのようになっているかを簡単に解説します.

2. Shadow DOM v1

Shadow DOM とは, ツールや命名規則がなくても, Vanilla JavaScript で CSS とマークアップをバンドルし, 実装の詳細を非表示にして, 自己完結型 (例えば, document.querySelector は, コンポーネントの Shadow DOM 内のノードを返しません) のコンポーネントを作成するための API です. 詳細は, 最後のセクションのリファレンスを参考にしてください.

Shadow DOM v0 では, createShadowRoot メソッドを利用して, Shadow Root を生成していましたが, v1 では, 以下のようなコードになります (<x-piano> v2.0.0 より).

export default class Piano extends HTMLElement {
    /** @override */
    constructor() {
        super();

        this.attachShadow({ mode : 'open' });
    }

    render() {
        this.shadowRoot.innerHTML = `...`
    }
        }

まず, HTMMLElement を継承したクラスを実装します. そして, attachShadow メソッドに, 引数 { mode : ‘open’ } を指定することで, Shadow Root が生成されます. あとは, Shadow DOM を生成するために innerHTML (や, appendChild) などの DOM 操作のメソッドを利用します.

3. Custom Elements v1

Custom Elements とは, その名前のとおり, 新しい HTML タグを作成したり, 既存の HTML タグを拡張したりするための API です. 詳細は, 最後のセクションのリファレンスを参考にしてください.

Custom Elements v0 では, document.registerElement というメソッドを利用して, Custome Elements を定義していましたが, Custom Elements v1 では, 以下のようなコードになります (<x-piano> v2.0.0 より).

export default class Piano extends HTMLElement {
    static get observedAttributes() {
        return [
            'ui-only',
            'type',
            'volume',
            'transpose',
            'glide',
            'attack',
            'decay',
            'sustain',
            'release'
        ];
    }

    constructor() {
        // 要素のインスタンスが作成またはアップグレードされたとき. 状態の初期化, イベントリスナーの設定, または, Shadow DOM の作成に利用します.
    }

    connectedCallback() {
        // 要素が DOM に挿入されるたびに呼び出されます. リソースの取得やレンダリングなどの, セットアップコードの実行に利用します.
    }

    disconnectedCallback() {
        // 要素が DOM から削除されるたびに呼び出されます. クリーンアップ コードの実行 (イベント リスナーの削除など) に利用します.
    }

    attributeChangedCallback() {
        // 属性が追加, 削除, 更新, または, 置換されたときに呼び出されます. また, そのためには, 対象の属性を指定する static get observedAttributes メソッドの実装が必要です.
    }
}

window.customElements.define('x-piano', Piano);

まずは, 拡張したい HTMLElement (例えば, HTMLButtonElement など) を指定して, クラスを実装します. あとは, window.customElements.define メソッドを利用して, タグ名を第 1 引数に, 対象のクラスを第 2 引数に指定して, Custom Elements を定義するだけです. これだけで, 最低限の実装は完了です. あとは, 必要に応じて, constructor や callback となるメソッドを実装するだけです.

4. ESModules

旧仕様の Web Components では, Custom Elements などを定義したファイルを読み込むには, HTML Imports という API を利用していましたが, 現在の仕様では, ESModules を利用するようになっています. API は非常に簡単で, script タグの type 属性に module を指定するだけです. また, ESModules に対応していないブラウザのために, nomodule 属性を利用した script タグも用意して, webpack などでバンドルしたフォールバックのスクリプトを読み込ませておきます. 具体的なコードは, 以下のようになります (<x-piano> v2.0.0 より).
定義した Custom Elements などを利用したい HTML のファイルで,

<script type="module" src="src/components/index.js"></script>
 <script nomodule src="build/app.js"></script>

のように, script タグを記述します.

5. References

4 年ぶりにメインサイトをリニューアルしました (レスポンシブ対応)

1. Overview

メディアクエリを利用せずにレスポンシブ対応をしました. その場合, 重要となるのが以下の 2 つです.

  • calc
  • max-width

では具体的に, どのように重要となるのか ? 各ページの対応ともに解説します.

2. Implement

2 – 1. Header, Profile, Skills, Footer

これらのページでは, テキストを中心に構成されているので, レスポンシブ対応をするためには, font-size をデバイス幅に応じて変更する必要がありました. メディアクエリを利用せずにこれを実現するには, calc を利用して, font-size を以下のように設定します. 以下の設定では, max-width: 1280px, min-width: 320px, 最小の font-size を 12px, 最大の font-size を 16px とした場合の値です.

font-size: calc(4 * ((100vw - 320px) / (1280 - 320)) + 12px);

例えば, iPhone 5 のような, 320px 幅のデバイスの場合, 100vw = 320px となるので, font-size は 12px となります. 逆に, デスクトップで, 1280px 以上あるような場合, 100vw = 1280px となるので, font-size は 16px となります. つまり, 4 というのは, 変化量で, 真ん中の () でくくられた算出式は, max-width と min-width から決定され, 最後の 12px というのは, 最小の font-size になるわけです. 一般化すると,

font-size: calc({変化量} * ((100vw - {min-width}) / ({max-width} - {min-width})) + {最小の font-size});

これを, html などに指定し, rem や em で font-size を指定すれば, メディアクエリを利用せずに, レスポンシブな font-size を実装できます.

2 – 2. Portfolio

ポートフォリオページでは, カルーセルパネルのレスポンシブ対応が必要でした. まず, カルーセルパネルのコンテナとなる要素 (サイトでは, .Portfolio__carousel) に, 必要な max-width を指定します. また, カルーセルパネルに入る個々の画像 (img タグ) には, max-width (デスクトップでの表示幅) と width: 100vw を指定します. CSS の設定はこれだけです.

次に, HTML タグ (JSX) と JavaScript の処理です. img タグの width / height 属性指定しないようにします (CSS の width: 100vw によって, 比率を保ったままレスポンシブになります). そして, アニメーションさせるためのオフセット値を, img 要素の width から算出するようにします. ただし, これには欠点があり, 初回の render 時に表示がずれてしまうことがあります.

const offset           = this.image ? this.image.width : 400;  // 初回は 400 (デスクトップと決め打ち)
const slideAmountRight = (slide * -offset) + offset;
const slideAmountLeft  = slide * offset;
const style            = {
    transform : `translateX(${slideAmountRight}px)`,
    left      : `${slideAmountLeft}px`
};

この問題の解決は, issue としておきたいと思います.

2 – 3. Music

ミュージックページでは, Grid Layout を採用していましたが, そもそもコンテンツが多すぎたので, スマートフォンのサイズになると, すべてのコンテンツが縦 (height: 100vh) におさまりきらないという問題がありました. そこで, モバイルファーストの考えに基づいて, コンテンツ (動画) を 2 つに絞りました. さいわい, YouTube は, 動画終了後に関連動画が表示されるのでそれで十分という判断でもありました. デスクトップでは, 横に並び, スマートフォンなどでは, 段落ちして縦に並ぶように実装しました. そのさいに, 動画のサイズもレスポンシブになるように, エラスティックビデオの実装もしました.

.Music__grid {
    /* デスクトップでは, 横に並び, スマートフォンなどでは, 段落ちして縦に並ぶようにするための Grid Layout の設定 */
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(288px, 1fr));
    grid-auto-rows: 1fr;
    grid-gap: 0;
}

/* 以下は, エラスティックビデオの実装に必要なスタイル */
.Music__grid > li {
    display: block;
    position: relative;
    padding-bottom: 56.25%;  /* 315 / 560 */
    height: 0;
    overflow: hidden;
}

.Music__grid > li > iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

2 – 4. ロゴの Retina 対応 + レスポンシブ対応

一般的に, 画像をレスポンシブ対応させるには, width / height 属性を指定せず, CSS で max-width: 100% を指定すればいいのですが, ロゴのように Retina ディスプレイでもきれいに見せたい場合, それだと意図した 2 倍のサイズで表示されてしまいます. しかし, 対応は簡単で, img タグの width 属性だけをオリジナルの画像の width の 1 / 2 に設定すれば, Retina 対応ができ, かつ, max-width: 100% によってレスポンシブ対応にもなります

3. Conclusion

メディアクエリを利用しないレスポンシブ対応の目的は, CSS の実装を複雑にしないことだと思います. しかし, 目的を忘れ, メディアクエリを利用しないことに固執し, 結果, CSS が複雑になってしまっては, 本末転倒です. ディアクエリを利用することは悪ではないので, 必要と判断したならば利用すべきだと思います (今回は, 私自身のトレーニングとして, CSS が複雑になってもメディアクエリを利用せずにチャレンジするというのが目的でしたので). もっとも, 今回実装をしてみて, よほどレイアウトが変わらない限り, たいていのケースでは calc と max-width でのりきれるのではないかと思いました !

4 年ぶりにメインサイトをリニューアルしました (デスクトップ版)

1. Overview

4 年ぶりにメインサイトをリニューアルしました (デスクトップ版のみ. レスポンシブ対応は今後のタスク). この 4 年間にフロントエンドをとりまく環境が大きく変わったことや, 自身のスキルもレベルアップしていることを同時に実感しました. そこで, 開発環境やリニューアルにおいて, 採用した技術などをまとめておきます.

1 – 1. Before renewal

https://weblike-curtaincall.ssl-lolipop.jp/

リニューアル前のサイトです (.htaccess で 301 リダイレクトをしているのでもう閲覧はできません). 制作したのは, 4 年前なので 2014 年です. フラットデザイン + レスポンシブデザインが流行していた頃ですね. したがって, 旧サイトもできるだけ, 画像はもちろん, CSS による装飾も少なくし, かつ, デスクトップ・タブレット, そして, スマートフォンとデバイスに応じて最適なレイアウトに変わるようになっています. 以下, 開発環境と採用した技術です.

1 – 1 – 1. Development

  • OS X のビルトイン Apache

1 – 1 – 2. Technologies

  • HTML, CSS (SCSS), JavaScript
  • jQuery, AngularJS
  • Flash, ActionScript 3.0
  • フラットデザイン, レスポンシブデザイン, アイコンフォント

jQuery, AngularJS, Flash … 時代を感じますね …

1 – 2. After renewal

https://korilakkuma.github.io/LIFE-with-FAVORITES/

リニューアル後のサイトです. ロリポップのサーバーから, GitHub Pages に移行したので, .htaccess で 301 リダイレクトをかけています. 今回のリニューアルにあたってチャレンジしたいこと 2 つありました.

  • 画面幅いっぱいに利用した, HTML 版プレゼンテーションスライドのようなレイアウト
  • メディアクエリを利用しない (極力利用しない) レスポンシブデザイン

特に, 「メディアクエリを利用しない (極力利用しない) レスポンシブデザイン」を実現するために, 旧サイトと比較して, かなりの無駄なコンテンツを削ぎ落としました. 以下, その例です.

  • トップページのお知らせ
  • プロフィールを簡素化
  • ポートフォリオはスクリーンショットのみ, かつ, カルーセルパネルを利用して, 狭い面積でできるだけ多くの情報を伝える
  • ポートフォリオと同様に, YouTube の埋め込み動画も Grid Layout を利用して, 簡潔に情報を伝える
  • 掲示板は, いったん廃止

1 – 2 – 1. Development

  • Babel
  • ESLint
  • stylelint
  • webpack + npm scripts
  • Docker

1 – 2 – 2. Technologies

  • HTML, CSS (PostCSS), JavaScript (ES2015 ~)
  • React
  • アクセシビリティ (WAI-ARIA 属性, role 属性)
  • レスポンシブデザイン (予定)

う〜ん … 時の流れを感じますね … 詳細は, リポジトリを参照してください.

2. Implement

それでは, 各ページ (ヘッダー, プロフィール, ポートフォリオ, スキル, ミュージック, フッター) ごとに簡単に実装を紹介します.

2 – 1. Header

ロゴとサブタイトルのみのコンテンツにしました. また, mouseenter すると, アニメーションが再開されるようになっています. React の処理も, state の操作ぐらいで特に難しいことはしていません. 唯一, ハマったことは, 最初にこのページを実装したのですが, width: 100vw; にしてしまうと, スクロールバーの横幅に依存しないので, 横スクロールバーが表示されてしまうことでした. したがって, 以下のようにして, ページを画面いっぱいに広げました

selector {
  width: 100%;
  height: 100vh;
}

2 – 2. Profile

ここも, レスポンシブ対応を意識して, コンテンツを削ぎ落としたぐらいで, 実装では特に難しいことはしていません.

2 – 3. Portfolio

このページのカルーセルパネルがもっとも苦労しました. jQuery で実装していたときは, アニメーションも jQuery (animate メソッド) が担うので比較的簡単でしたが, React になると, アニメーションは CSS に分離させるので, jQuery でカルーセルパネルを実装する場合の考えと, React + CSS でカルーセルパネルを実装する場合の考えが根底から異なるのが原因でした.

ループしないタイプのカルーセルパネルはなんとか自力で実装できたのですが, ループするタイプは実装がおよばなかったので, 以下のカルーセルパネルを参考にしました.

ポイントは, Flexbox の order を適切に入れ替えているところでしょうか. また, transition の duration は, アニメーションの質を損なわない範囲で, できるだけ短く設定するのがポイントでした. リニューアルサイトでは transition: transform 0.3s ease-out; で設定しています. これはなぜかと言うと, あまり長く設定すると, order を入れ替えたときに, サイドのパネルが消えるのがもろに見えてしまうからです. 短くすることで, 人間の感覚上, 消えてないように見せかけています.

2 – 4. Skills

ここも, レスポンシブ対応を意識して, コンテンツを削ぎ落としたぐらいで, 実装では特に難しいことはしていません.

2 – 5. Music

CSS の比較的新しいスタイルである, Grid Layout を導入しました (もしかしたら, Edge はダメかも …). 最も見てもらいたい動画 (Forever Love (Piano Instruments)) を左の最も面積の広い部分に配置し, 残りをグリッド (格子状) に配置しています. なんだか仕様が複雑そう (Flexbox のような印象をもっていました …) と思いきや, 実装してみると, 意外とシンプルでした.

.Music__grid {
    margin-top: 12px;
    display: grid;
    grid-template-columns: 560px 280px 1fr;
    grid-template-rows: 158px 1fr;
}

.Music__grid > li {
    display: block;
}

.Music__grid > li:first-child {
    grid-column-start: 1;
    grid-column-end: 2;
    grid-row-start: 1;
    grid-row-end: auto;
}

.Music__grid > li:nth-child(2) {
    grid-column-start: 2;
    grid-column-end: 3;
    grid-row-start: 1;
    grid-row-end: 2;
}

.Music__grid > li:nth-child(3) {
    grid-column-start: 3;
    grid-column-end: 4;
    grid-row-start: 1;
    grid-row-end: 2;
}

.Music__grid > li:nth-child(4) {
    grid-column-start: 2;
    grid-column-end: 3;
    grid-row-start: 2;
    grid-row-end: 3;
}

.Music__grid > li:nth-child(5) {
    grid-column-start: 3;
    grid-column-end: 4;
    grid-row-start: 2;
    grid-row-end: 3;
}

2 – 6. Footer

お問い合わせフォームがメインです. 旧サイトでは, AngularJS で実装していました. ここも, React の state 管理をちゃんと実装するぐらいで特に難しいことはしていません.

3. Future Tasks

今後のタスクとしては, レスポンシブ対応です. 特に, メディアクエリを使わない (or 極力使わない) ように実装したいです (メディアクエリは, CSS を複雑にしてまうので). その場合, おそらく困難になるのが, ポートフォリオページのカルーセルパネルと, ミュージックページの Grid Layout でしょう.

4. Conclusion

長らく手をつけていないプロダクトをリニューアルすることは, 最新技術の習得にもなりますし, なにより時代の流れを感じて楽しいです !

Go合宿2018 に参加してきました

1. Overview

昨日, 今日とGo合宿2018 に参加してきたのでその成果と感想を記載しておきます.

1 – 1. Targets

合宿に参加した目的は, 大きなところでは, 以下の 3 つとなります.

  • X Sound の WebSocket Server を Node.js から Go に変えると, どのくらいパフォーマンスが向上するか実験してみたい. そして, 短期間で集中的にその実装をしたい
  • 合宿を機会に, 仕事であれ, 趣味であれ, 日常的に Go を利用するようにしたい
  • 普段は, フロントエンドよりの勉強会や LT の参加ばかりなので, サーバーサイドやバックエンド系のイベントにも参加してみたい

1 – 2. Career of Go

2 年ほど前に, 今の会社 (サーバーサイドは Go を利用しているとうかがったので) に転職するのを機会に準備として 1 ヶ月ほど独学で学んだ程度です. また, 肩書きは, フロントエンドエンジニアなので, 日常的に Go は利用しておらず, 2 週間に一度, 事業の制度としてある FIL という日に Go を少し業務で利用するぐらいでした.

2. Development

先に開発した, 最終成果であるリポジトリを紹介しておきます.

package main

import (
	"fmt"
	"golang.org/x/net/websocket"
	"net/http"
	"os"
	"strconv"
)

func main() {
	if len(os.Args) != 2 {
		println("Require port number !")
		os.Exit(1)
	}

	port, err := strconv.ParseUint(os.Args[1], 10, 16)

	if err != nil {
		println("Invalid port number !")
		os.Exit(1)
	}

	var websockets []websocket.Conn

	http.Handle("/", websocket.Handler(func(ws *websocket.Conn) {
		var in []byte

		websockets = append(websockets, *ws)

		for {
			if err = websocket.Message.Receive(ws, &in); err != nil {
				println(err)
				break
			}

			for _, v := range websockets {
				if v != *ws {
					websocket.Message.Send(&v, in)
				}
			}
		}
	}))

	err = http.ListenAndServe(fmt.Sprintf(":%d", port), nil)

	if err != nil {
		panic(err)
	}
}

実質は, 50 行のコードです (w).

ポイントや, つまづいた点を解説します.

まず, WebSocket のパッケージには golang.org/x/net/websocket を利用しました. Go にも様々な WebSocket のパッケージがあるようなのですが, 選定する利用 (条件) として以下の 2 つがありました.

  • バイナリメッセージングが可能である
  • クライアントサイドの実装には影響しない

後者が少しわかりづらいと思うので, もう少し詳細を解説します. WebSocket による処理は, 当然, サーバーサイドだけでなく, クライアントサイドも関わってくるわけですが, クライアントサイドの WebSocket 処理は, すでに XSound の Session クラスが担っています. したがって, JavaScript の WebSocket ライブラリの socket.io のようにクライアントサイドも変更しないといけないとなると, XSound も実装を変更し, また, XSound の思想の 1 つである, 特定のライブラリやフレームワークに依存しないということも破綻してしまうので, 非常に重要な理由でありました.

つまづいた点としては,

for _, v := range websockets {
	if v != *ws {
		websocket.Message.Send(&v, in)
	}
}

の部分で, バイナリメッセージング (X Sound のセッション機能) において, 自身のソケットに送信しては, 音が二重に鳴ってしまうので, 自身のソケットでないかどうかを判定する必要があるのですが, Go のポインタをゴリゴリしたことない私にとっては意外とつまづきました …

3. Future tasks

実装した Go の WebSocket Server を Heroku にデプロイまではできたのですが, どうしても TLS handshake error が解決できず, Heroku の Node.js とローカルでの Go での比較しかできず, 厳密ではありませんが, Go のほうがやはり体感的にパフォーマンスは向上した (音がよくなった) 気がします w

とある方の感想です w

ちなみに, X Sound は https で動作しているので, WebSocket も wss を利用する必要がありました. したがって, Go のコードも以下のように変更して Heroku にデプロイしました …

package main

import (
	"fmt"
	"golang.org/x/net/websocket"
	"net/http"
	"os"
	"strconv"
)

func main() {
	port, err := strconv.ParseUint(os.Getenv("PORT"), 10, 16)

	if err != nil {
		println("Invalid port number !")
		os.Exit(1)
	}

	fmt.Printf("PORT: %d\n", port)

	var websockets []websocket.Conn

	http.Handle("/", websocket.Handler(func(ws *websocket.Conn) {
		var in []byte

		websockets = append(websockets, *ws)

		for {
			if err := websocket.Message.Receive(ws, &in); err != nil {
				println(err)
				break
			}

			for _, v := range websockets {
				if v != *ws {
					websocket.Message.Send(&v, in)
				}
			}
		}
	}))

	http.ListenAndServeTLS(fmt.Sprintf(":%d", port + 0), "server.crt", "server.key", nil)
        http.ListenAndServeTLS(fmt.Sprintf(":%d", port + 1), "server.crt", "server.key", nil)
	http.ListenAndServeTLS(fmt.Sprintf(":%d", port + 2), "server.crt", "server.key", nil)
	http.ListenAndServeTLS(fmt.Sprintf(":%d", port + 3), "server.crt", "server.key", nil)
}

これは, 近日中に解決して, なんとか Go の WebSocket Server で X Sound のセッション機能を楽しんでもらいたいです !

4. Conclusion

ローカルでしか動作をさせることができませんでしたが, Go のバイナリメッセージングの WebSocket Server を実装して, デモをするという最低限の目標は達成できました. また, メルカリの方から DDD の本質をとてもわかりやすくプレゼンしていただき, 思わぬ収穫となりました. 業務とコードの乖離をできるだけ少なくする, また, DDD は銀の弾丸ではなく向き不向きがあることなどなど … そして, 旅館も, また, 旅館の従業員の方々も非常によくて, なかなかの開発環境でした. おそらく, 来年も開催されると思いますので, ぜひ参加したいですね. そのときには, もっともっとレベルアップしてよりよりプロダクトを実装できるようになっていたいです !

MacBook Pro 2017

1. Overview

MacBook Pro 2017 を購入して約 1 ヶ月が経過したので, その使用感を書いておきたくなりました.
ちなみに, 2 台目の Mac で, MacBook Pro を購入するのは初めてです. 以前は, つまり, 最初の Mac は, MacBook Air で 2014 年 3 月 9 日から, 2018 年 5 月 26 日まで, 約 4 年間お世話になりました. 性能や使用感は悪くなかったのですが, SSD 128 GB と容量がかなりきつくなっていたのと, あとは, バッテリーの減りがかなり早くなってきたことが買い替えるきっかけでした.

2 台目の Mac, つまり, MacBook Pro 2017 は, 13 インチのスペースグレイ, Vim を使うので Touch Bar はなし, SSD 256 GB にしました.

2. Feeling of use

2 – 1. CPU

さすがに, MacBook Pro だけあって処理速度は体感できるほど違います. 例えば, Web Audio API で比較的長い楽曲をデコードするのも MacBook Air ではそれなりに時間を要していたのが, 一瞬です.

2 – 2. Display

MacBook Air は Redina ディスプレイではなかったせいか, Retina ディスプレイの MacBook Pro は, 美しい …

2 – 3. Keyboard

MacBook Pro 2017 に買い替える際に, 最も懸念していたのが, バタフライキーボードでした. 最初は, タッチが浅いことに違和感を覚えるかもしれないですが, 慣れると断然使いやすいです. ただ, タッチの音が大きいのは少し気になりますが …

2 – 4. Appearance

はじめは, シルバーを購入しようと思っていたのですが, 売り場でスペースグレイを見て, スペースグレイに即決しました. ずっともっていたくなるようなかっこよさと美しさを兼ね備えています.

3. Built in Apache

これは, MacBook Pro 2017 の問題ではなく, macOS High Sierra の問題なのですが, ビルトインの Apache の設定を少し変える必要がありました (ちょっとしたサンプルコードを動かすときなどは, ビルトインの Apache で動作させるのでよく使っています …).

まずは, Apache の設定です.

$ sudo vim /etc/apache2/httpd.conf

173 – 176 行目がコメントアウトされていたら, コメントを外します

LoadModule userdir_module libexec/apache2/mod_userdir.so
LoadModule alias_module libexec/apache2/mod_alias.so
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
LoadModule php7_module libexec/apache2/libphp7.so

510 行目がコメントアウトされていたら, コメントを外します

Include /private/etc/apache2/extra/httpd-userdir.conf

544 行目がコメントアウトされていたら, コメントを外します

Include /private/etc/apache2/users/*.conf

次に, ユーザーごとの設定です.

$ vim /etc/apache2/users/{ユーザー名}.conf
<Directory /Users/{ユーザー名}/Sites>                                                                                             
    Options Indexes MultiViews FollowSymLinks Includes
    AllowOverride All
    Require all granted
    Order allow,deny
    Allow from all
</Directory>

最後に, Apache を起動します.

$ sudo /usr/sbin/apachectl start

これでブラウザから, http://localhost/~{ユーザー名}/ でアクセスできるようになります.

最後に … High Sierra では, キーチェーンが SSH のパスフレーズを憶えてくれず, 毎回入力しなくてはいけなくてはいけなくめんどくさいので,

$ vim ~/.ssh/config

を作成して,

Host *
  UseKeychain yes
  AddKeysToAgent yes

と入力しておけば, 毎回入力する手間が省けます.