コンパイラかく語りき

import { Fun } from 'programming'

自分用 CSS Grid チートシート

CSS グリッドレイアウトに関するチートシートです。自分用なので結構雑です。

developer.mozilla.org

用語

Grid container

グリッドコンテナ。グリッドレイアウトを敷くもの。

Grid item

グリッドコンテナに包含されるアイテム。

Grid column

レイアウト上の列。

Grid row

レイアウト上の行。

Grid track

グリッドトラック。レイアウト上の縦線と横線のこと。 4x4なレイアウトなら、+ のようなトラックが引かれる。

Grid area

グリッドエリア。任意のグリッドトラックに囲まれた領域。

Grid cell

グリッドセル。グリッドトラックをまたぐこともあるグリッドエリアとは異なり、単一のグリッド領域を指す。

プロパティ

コンテナ系

display

Grid レイアウトを使うために指定してなくてはいけないもの。

.container {
  // ブロックレベルのグリッドコンテナを敷く。
  display: grid; 

  // インラインレベルのグリッドコンテナを敷く。
  display: inline-grid: 
}

grid-template-columns, grid-template-rows

それぞれ、行と列のサイズを指定する。

.container {
  // 1列目が60px, 2列目が80pxになる
  grid-template-columns: 60px 80px; 

  // 1行目が40px, 2行目が4em, 3行目が40pxになる
  grid-template-rows: 40px 4em 40px; 
}

grid-template-area

名称付きグリッドエリアを指定する。

.container {
  // 左上4マスを"a", 中央下1マスを"b", 右下1マスを"c" と指定
  grid-template-areas: 
            "a a ."
            "a a ."
            ". b c";
}

grid 系の他のプロパティから利用可能。

grid-template

grid-template-columns, grid-template-rows, grid-template-areas のショートハンド。

.container {
  grid-template: 
            "a a ." minmax(50px, auto)
            "a a ." 80px
            "b b c" auto / 2em 3em auto;
}

grid-column-gap, grid-row-gap

水平と垂直のグリッドトラックの幅を指定。

.container {
  // 列グリッドトラックの幅を10pxにする
  grid-column-gap: 10px;

  // 行グリッドトラックの幅を10pxにする
  grid-row-gap: 15px;
}

grid-gap

grid-column-gap, grid-row-gap のショートハンド。

.container {
  // グリッドトラックの幅を、列15px, 行10px で指定
  grid-gap: 15px 10px;
}

justify-items

グリッドアイテムの水平方向の寄せ方を指定。

.container {
  // 中央寄せ
  justify-items: center;
  
  // 始点寄せ(始点が左なら左寄せ)
  justify-items: start;

  // 終点寄せ(終点が右なら右寄せ)
  justify-items: end;

  // 他にも色々ある
}

align-items

グリッドアイテムの垂直方向の寄せ方を指定。

.container {
  // 中央寄せ
  align-items: center;
  
  // 始点寄せ(始点が左なら左寄せ)
  align-items: start;

  // 終点寄せ(終点が右なら右寄せ)
  align-items: end;

  // 他にも色々ある
}

place-items

justify-items と align-items のショートハンド。

.container {
  // 垂直方向には中央寄せ、水平方向には終点寄せ
  place-items: center end;

  // 両方中央寄せ
  place-items: center;
}

justify-content

グリッドコンテナ自体を水平方向にどのように寄せるか。

.container {
  // 水平方向に中央寄せ
  justify-content: center: 
}

justify-content, align-content, place-content

それぞれ、justify-items, align-items, place-content のグリッドコンテナ版。

grid-auto-columns, grid-auto-rows

(未完)

grid-auto-flow

位置指定のゆるいグリッドアイテムたちをどのように自動敷き詰めするか指定する。

.container {
  // (グリッドアイテムの順番を保ちつつ)水平方向で敷き詰め
  grid-auto-flow: row;

  // (グリッドアイテムの順番を保ちつつ)垂直方向で敷き詰め
  grid-auto-flow: column;

  // グリッドアイテムの順番を変えてでも敷き詰め
  grid-auto-flow: dense;
}

アイテム系

grid-column-start, grid-column-end, grid-row-start, grid-row-end

グリッドアイテムを、グリッドレイアウト上のどこに置くか指定する。

grid-column, grid-row

それぞれ、(grid-column-start + grid-column-end), (grid-row-start, grid-row-end) のショートハンド。

grid-area

グリッドアイテムに名前を与える。

.a-item {
  grid-area: a; // ".a-item" が4マス分のグリッドエリア "a" を占める
}

また、grid-column-start, grid-column-end, grid-row-start, grid-row-end のショートハンドでもある。

.item {
  grid-area: 1 / col4-start / last-line / 6
}

justify-self

グリッドアイテム自身の水平方向の寄せ方を指定。

.item {
  // 自身をグリッドセル内で中央寄せ
  justify-self: center;
}

align-self

グリッドアイテム自身の垂直方向の寄せ方を指定。

.item {
  // 自身をグリッドセル内で中央寄せ
  align-self: center;
}

place-self

justify-self, align-self のショートハンド。

.item {
  // 自身をグリッドセル内で、垂直方向に中央寄せ、水平方向に終点寄せ
  place-self: center end;
}

React v16.6.0で追加された lazy, memo そして contextType を試してみる

React.js v16.6.0 にて追加された新機能を試してみました。

reactjs.org

環境づくり

サクッと、create-react-app を利用しました。

create-react-app - github.com

$ npm -g install create-react-app

$ npx create-react-app my-app

$ cd my-app

$ yarn start

初めて create-react-app を利用したのですが、簡単すぎてビビりました…。React で何か試したい時は積極的に使っていきたいですね。

React.memo

プレゼンテーションコンポーネント(独自に state を保持したり、ライフサイクル関数を使うことなく、描画の行うだけのコンポーネント)を定義するする時は、クラスコンポーネントではなく SFC を利用しています。

import React from 'react';

// ふつーの SFC
const Hello = () => {
  return (
    <div>Hello React v16.6!</div>
  )
}

しかし、最近業務で引っかかった問題として、SFC で不要な再レンダリングが起こっていました。

レンダリングの原因は、空配列であった state に空配列を再代入していたことでした。値は変わっていないのに、参照が変わっていたため、SFC が props に更新ありと判定していました。

参考リンク: optimizing-performance - reactjs.org

そこで、PureComponent への書き換えを行いました。

PureComponent は props を shallow 比較するのでパフォーマンス向上に寄与しました。

React.memo を使うと、SFC を PureComponent に書き換える事なく同様のことが実現できます。

参考リンク: React.memo - reactjs.org

import React from 'react';

// ふつーの SFC
const Hello = (props) => {
  return (
    <div>Hello React v16.6!</div>
  )
}

// props を shallow 比較するコンポーネント
const PureHello = React.memo(Hello)

クラスへの書き換えの手間がかからなくて良さげです。

React.Suspense と React.lazy

ここで、API フェッチを行い、そして待ち合わせを行うようなコンポーネントのケースを考えてみます。 従来ならこのように書いていました。

import React, { Component } from 'react';

// 与えられた Todo を表示する
const Todo = (props) => {
  if(props.isLoading) {
    return <div>Loading...</div>
  }
  return (
    <div>{ props.todo.title }</div>
  )
}

// Todo のフェッチを行い、フェッチ状況も管理する
class App extends Component {
  state = {
    isLoading: false,
    todo: null
  }

  componentDidMount() {
    this.setState({ isLoading: true })

    fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(res => res.json())
      .then(json => {
        this.setState({ isLoading: false, todo: json })
      })
  }
  
  render() {
    return (
        <Todo isLoading={this.state.isLoading} todo={this.state.todo} />
    );
  }
}

React.Suspense を使って書くと以下のようになります。(多分、細部では違った処理になっているかもしれません)

import React, { Component, Suspense } from 'react';

const Todo = (props) => {
  if(todo === null) {
    throw new Promise((resolve, reject) => {
      fetch(`https://jsonplaceholder.typicode.com/todos/${props.id}`)
        .then(res => res.json())
        .then(json => {
          todo = json
          resolve()
        })
        .catch(e => {
          console.warn("Not fetched yet")
          reject()
        })
    })
  }

  return (
    <div>{ todo.title }</div>
  )
}

class App extends Component {
  render() {
    return (
      {/* Suspense にフェッチ完了までのリンダリングを引き受けさせる*/}
      <Suspense fallback={<div>Loading...</div>}>
        <Todos id={1} />
      </Suspense>
    );
  }
}

参考URL: React 16.6で追加されたReact.Suspenseについて - qiita.com

さらに、この Suspense ですが、React.lazy を使った dynamic import によって Code splitting を噛ませることもできるようです。

import React, { Component, Suspense, lazy } from 'react';

const Todo = lazy(() => import('path/to/Todo.jsx'))

class App extends Component {
  render() {
    return (
      <Suspense fallback={<div>Loading...</div>}>
        <Todo id={1} />
      </Suspense>
    );
  }
}

export default App;

SSRではまだ使えないようですが…。

参考URL:

static contextType

Context は以前から実装されていた機能ですが(実はあんまり使ったこと無い)、 クラスの static 変数として context にアクセスできるようになったみたいです。

import React, { Component } from 'react';

const MyContext = React.createContext("default value");

class MyClass extends React.Component {
  static contextType = MyContext;

  render() {
    console.log(this.context) // "default value"
    return (
      <div>{ this.context }</div>
    )
  }
}

参考URL: static contextType - reactjs.org

static getDerivedStateFromError()

従来の componentDidCatch に加えて、 getDerivedStateFromError が追加されました。

componentDidCatch の問題点は、SSR に対応していないことでした。 getDerivedStateFromError は将来的には SSR 対応されるようなので、ユニバーサルなアプリケーションには役立ちそうです。

参考URL: static getDerivedStateFromError()

以上、 React v16.6 で追加された機能について試したり、調べたりしてみました。

cloudfoundry/nodejs-buildpack v1.6.19にて、yarnによるpackage installがこける問題と対処

nodejs-buildpack v1.6.19

2018/02/26、cloudfoundyのnodejs-buildpackがv1.6.19へとバージョンアップデートされました。

変更: nodejs-buildpack/manifest.yml - Add yarn 1.3.2, remove yarn 1.2.1 [#155490241]  

この変更によって、buildpackが依存するYarnのバージョンがv.1.3.2へと変更されました。

しかし、Yarn v1.3.2には以下の問題が含まれているようです。

 Yarn 1.3.2 incorrectly bypasses HTTP(S)_PROXY #4885  

実際に起こった問題

yarn installがこけました。

結果、buildpackのコンパイルが正常終了せずに、cf push自体がfail。appがstartしないという事態に。

対処

manifest.ymlにて、buildpackのバージョンを明示的に指定しました。

---
  ...
  buildpack: https://github.com/cloudfoundry/nodejs-buildpack.git#v1.6.18

Cloud Foundry Documentation - Deploying with Application Manifests#buildpack

nodejs-buildpack v1.6.18はYarn v1.2.1に依存しているので、パッケージインストール時に上記の問題が起こることはありませんでした。

【Cocoapods】 pod setupにて error: RPC failed; curl 56 SSLRead() return error -36| 2.27 MiB/s

発端

以下の記事に従っていた所、エラーが。

【Swift】CocoaPods導入手順 - Qiita

$ pod setup

Setting up CocoaPods master repo
  $ /usr/bin/git clone https://github.com/CocoaPods/Specs.git master --progress
  Cloning into 'master'...
  remote: Counting objects: 1827856, done.
  remote: Compressing objects: 100% (188/188), done.
  error: RPC failed; curl 56 SSLRead() return error -36| 2.27 MiB/s
  fatal: The remote end hung up unexpectedly
  fatal: early EOF
  fatal: index-pack failed
[!] /usr/bin/git clone https://github.com/CocoaPods/Specs.git master --progress

Cloning into 'master'...
remote: Counting objects: 1827856, done.
remote: Compressing objects: 100% (188/188), done.
error: RPC failed; curl 56 SSLRead() return error -36| 2.27 MiB/s
fatal: The remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed

解決

以下のStackoverflowの回答に従い、解決。

RPC failed; curl 56 SSLRead() return error -9806 MiB/s - stackoverflow.com

$ git config --global http.postBuffer 2M

gitのバッファを増やしました。

Setting up CocoaPods master repo
  $ /usr/bin/git clone https://github.com/CocoaPods/Specs.git master --progress
  Cloning into 'master'...
  remote: Counting objects: 1827856, done.
  remote: Compressing objects: 100% (189/189), done.
  remote: Total 1827856 (delta 90), reused 35 (delta 35), pack-reused 1827623
  Receiving objects: 100% (1827856/1827856), 503.62 MiB | 2.03 MiB/s, done.
  Resolving deltas: 100% (1001375/1001375), done.
  Checking out files: 100% (206551/206551), done.

CocoaPods 1.4.0.rc.1 is available.
To update use: `sudo gem install cocoapods --pre`
[!] This is a test version we'd love you to try.

For more information, see https://blog.cocoapods.org and the CHANGELOG for this version at https://github.com/CocoaPods/CocoaPods/releases/tag/1.4.0.rc.1

Setup completed

成功。

余談

pod setup がやっていることはCocoaPodsのレポジトリをcloneしているようなので、困ったらManualでcloneするのも手かもしれませんね。

Stackoverflowの回答より。

Finally, if https does not want to cooperate, try the ssh url:

cd  ~/.cocoapods/repos 
git clone git@github.com:CocoaPods/Specs.git

jQuery脱却の第一歩 doc-readyをつかってみる

クライアントのJSを書くとき、DOMのロードを待つ必要がある時があります。そんなとき、jQueryなら、以下のように書きますね。

$(document).ready(function() {
  // your code comes here...
})

ただ、JSの小さなモジュールを作るためにいちいちjQueryを使うのかは悩みどころです。 DOMの取得やイベントの付与には、document.getElementByIdやaddEventListenerで事足りてしまうことがあります。

DOMのロード完了待機にも、jQuery以外の方法を使ってみましょう。

doc-ready

npmのパッケージにdoc-readyというものがあります。今回はこれを使ってみます。

公式の説明によると、

Cross browser document ready helper. Supported by IE8+ and good browsers.

とのこと。IE8にも対応しているのが特徴のように見えます。

使い方

まずはdoc-readyをインストール。

$ npm i doc-ready --save

JSファイルで以下のように書きます。

// main.js
import docReady from 'doc-ready'

docReady(() => {
  // your code comes here...
}) 

内部処理

内部的には、以下のようなことを行っています。

  1. docReady.isReadyというプロパティを保持。以下の処理が用意されており、それぞれがisReadyをtrueに変更します。

    • document.readyState === ‘complete’
    • documentのDOMContentLoadedあるいは、readystatechangeイベントが発生する。
    • windowのloadイベントが発生する。
  2. isReady === trueならば、渡された関数(上の//your code comes here…部分)を実行。

また、IE8に対応するため、イベントのバインドにはeventieというパッケージのbindを利用しています。

ライブラリのソースコードこちらです。

これで、jQueryに頼らずにDOMのロード完了を待つことができます。 IE8に対応しない場合は、documentのDOMContentLoadedイベントを使えば良さそうです。

rethinkdbdashを試してみる

RethinkDBのNodeドライバと言えば、公式のドライバがあります。もちろんこれは不満なく使うことができるのですが、どうやらrethinkdbdashという別のドライバもあるようです。

この記事ではrethinkdbdashの特徴や機能について、READMEを訳す形でメモしていきます。

存在意義

rethinkdbdashは、promisesとconnection poolを実験的に使用するために作られました。promiseは公式のドライバにも採用され、スタンスとしては公式ドライバの強化であるようです。

公式ドライバとの違い

1. モジュールロード時に実行する必要がある

// official
const r = require('rethinkdb') 

// rethinkdbdash
const r_not_called = require('rethinkdbdash') // Not enough... [Function: main]
const r_called     = require('rethinkdbdash')() // OK!

2. connectメソッドを呼ぶ必要が無い

ロード時の実行の際、自動的にconnectが行われます。便利ですね。

const r = require('rethinkdbdash')() 
// Creating a pool connected to localhost:28015

3. cursorがArrayになっている

const r = require('rethinkdbdash')()

r.table('user').run().then((result) => {
    console.log(Array.isArray(result)) // true
})

公式ドライバからの移行

完全対応する場合

// これを
var r = require('rethinkdb')

// こうする
var r = require('rethinkdbdash')({
    pool: false,
    cursor: true
})

rethinkdbdashの機能を活かしたい場合

// まずロードを書き換え
var r = require('rethinkdb') // 旧
var r = require('rethinkdbdash')() // 新

// コネクションに関する記述を削除
r.connect({host: ..., port: ...}).then(function(connection) {
    connection.on('error', handleError);
    query.run(connection).then(function(result) {
        // console.log(result);
        connection.close()
    });
}) // 旧

query.run().then(function(result) {
    // console.log(result);
}) // 新

// カーソルに関する記述を削除
r.table('data').run(connection).then(function(cursor) {
    cursor.toArray().then(function(result) {
        // console.log(result):
    })
}) // 旧

r.table('data').run().then(function(result) {
    // console.log(result);
}) // 新

TLSコネクション

RethinkDBはTLSコネクションをサポートしていませんが、以下の手順で実現できます。(※この機能はエクスペリメンタルです)

まず、サーバにTLSプロキシを立てます。

var tls = require('tls');
var net = require('net');
var tlsOpts = {
    key: '', // プライベートキー
    cert: '' // 公式証明書
};
tls.createServer(tlsOpts, function (encryptedConnection) {
    var rethinkdbConn = net.connect({
        host: 'localhost',
        port: 28015
    });
    encryptedConnection.pipe(rethinkdbConn).pipe(encryptedConnection);
}).listen(29015);

それから、安全な接続を行います。

var r = require('rethinkdbdash')({
  port: 29015,
  host: 'place-with-no-firewall.com',
  ssl: true
});

新機能

ドライバインポート

ドライバのインポート時に、以下のオプションを渡すことができます。

  • db: \ - 指定がない場合はデフォルトDBが使われます。
  • user: \ - RethinkDBユーザー。デフォルトはAdmin。
  • password: \ - ユーザーのパスワード。デフォルトは空文字。
  • discovery: \ - デフォルトではfalse。trueならば、定期的にserver_statusテーブルからデータをプルしてホスト一覧を最近に保ってくれる。
  • pool: \ - falseにするとコネクションプールを使わない。
  • buffer: \ - プールで利用可能なコネクションの最小数。デフォルトは50。
  • max: \ - プールで利用可能なコネクションの最大数。デフォルトは100。
  • timeout: \ - コネクションが開かれる時間を指定。デフォルトは20。
  • pingInterval: - もし0以上が指定されたら、コネクションはpingInterval秒ごとにpingされる。デフォルトは-1。
  • timeoutError: \ - エラー時の再接続間隔時間。デフォルトは1000。
  • timeoutGb: \ - 不使用のコネクションをどのくらい保持しておくか。デフォルトは60601000
  • silent: \ - console.error errors。デフォルトはfalse。
  • servers: {host: \, port: \}のArray - 接続するRethinkDBノード。
  • optionalRun: \ - falseにすると結果を残さないクエリ実行。デフォルトはtrue。

シングルインスタンスの場合、hostportを直接渡すことも可能。

// localhost:8080に接続し、他のインスタンスも発見できるようにさせる。
var r = require('rethinkdbdash')({
    discovery: true
});

// localhost:8080にだけ接続する。
var r = require('rethinkdbdash')();

// コネクションプールを作成しない。
var r = require('rethinkdbdash')({pool: false});

// `192.168.0.100`, `192.168.0.101`, `192.168.0.102`から送られてくるクラスタに接続する。
var r = require('rethinkdbdash')({
    servers: [
        {host: '192.168.0.100', port: 28015},
        {host: '192.168.0.101', port: 28015},
        {host: '192.168.0.102', port: 28015},
    ]
});

// `192.168.0.100`, `192.168.0.100`, `192.168.0.102` を含むクラスタに接続しつつ、最大接続数を3000に引き上げ300の接続を利用可能にする。
var r = require('rethinkdbdash')({
    servers: [
        {host: '192.168.0.100', port: 28015},
        {host: '192.168.0.101', port: 28015},
        {host: '192.168.0.102', port: 28015},
    ],
    buffer: 300,
    max: 3000
});

コネクションプール

クエリ発行後にNodeのscriptを閉じたい場合には、プールをdrainする必要がある。

r.getPoolMaster().drain();
var r = require('rethinkdbdash')();

const userTable = r.table('user').run()

r.getPoolMaster().drain()

userTable.then((res) => {
    console.log(res)
})
// Unhandled rejection ReqlDriverError: None of the pools have an opened connection and failed to open a new one.

また、プールマスターはerrorやnewステートをstderr上でロギングする。 logイベントに対してリスナをバインドすることで、ログを扱うことができる。

r.getPoolMaster().on('log', console.log);

その他、プールマスターを通じて実行できるメソッド。

var r = require('rethinkdbdash')();

const userTable = r.table('user').run()
const pointTable = r.table('point').run()

// オープンなコネクションの数。
console.log(r.getPoolMaster().getLength()) // 2

// アイドルなコネクションの数。クエリ実行中でないもの。
console.log(r.getPoolMaster().getAvailableLength()) // 0

// すべてのプールにアクセスできる。
console.log(r.getPoolMaster().getPools())

ストリーム

(TODO: 時間切れのため、あとで追記します。)