ECMAScript 2017、2018 の時代の開発環境


## 概要

ECMAScript 2017、2018 で導入が予定されている機能および開発ツールについて調べました。

## ES モジュール
主要ブラウザー、モジュールバンドラー (Webpack、Rollup) でサポートされています。Node.js は v8.5 で実験的な機能として利用できます (`--experimental-modules`)。古いバージョンの Node.js のサポートのために　`@std/esm` が公開されています。

ES モジュールを利用するメリットは従来よりも読み込む必要のあるコードを減らすことができることです。必要なクラスや関数だけをインポートできます。RxJS の場合、どれだけ変わるのかは[こちら](http://qiita.com/clockmaker/items/5e2207b14dac97c4ede1)の記事をご参照ください。

```js
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
```

今後の課題は ES モジュールをサポートしないレガシーブラウザーへの対応です。`type="module"` および `nomodule` を指定することで ES モジュールをサポートするブラウザー、サポートしないブラウザーのためのコードを使いわけることができます。

```html
<!-- https://stackoverflow.com/a/45947601/531320 -->
<!-- https://html.spec.whatwg.org/multipage/scripting.html#script-nomodule-example -->
<script type="module" src="app.js"></script>
<script nomodule src="classic-app-bundle.js"></script>
```

今後の課題は ES2018 で導入される可能性の高い dynamic import() のサポートです。

## ECMAScript 2017

**Object.values() と Object.entries()**

それぞれプレーンなオブジェクトから列挙可能な配列を返します。

```js
var obj = {a: 1, b: 2, c: 3};
for (let [key, value] of Object.entries(obj)) {
    console.log(key + ' ' + value);
    // "a 1", "b 2", "c 3"
}
```

`for-in` ループと `hasOwnProperty` によるチェックを組み合わせて書いていた ECMAScript 5 以前の時代と比べて、コードが書きやすくなったことを象徴するメソッドです。

**async/await**

async/await は Promise を使ったコードを読みやすくします。try-catch 構文だけでなく、await-catch の書き方をする人も見られます。

```js
async function run() {
  const request = fetch(url, opt).then(res => res.text());
  const text = await request.catch(err => console.log(err));
  console.log(text);
}

run();
```

HTTP ライブラリやフレームワーク、テストツールを開発する場合、async/await を全面的に使えるようにすることが求められています。2017年の夏に HTTP クライアントの request の開発者が [r2](https://github.com/mikeal/r2) という名前の新しいプロジェクトをはじめたことが注目されました。

## ECMAScript 2018

2ality に[まとめ記事](http://2ality.com/2017/02/ecmascript-2018.html) があります。アクティブな提案は [tc39/proposals](https://github.com/tc39/proposals) にまとまっています。

**dynamic import()**

ロケールなどの条件に応じてスクリプトを読み込むことができるようになります。


```js
async function main() {
  const locale = getLocale();
  const file = `message_${locale}.js`
  const myModule = await import(file).then(...);
}

main();
```

2017年9月時点で import() をサポートしているのは　TypeScript および Webpack 、Babel です。ブラウザーの実装がないことから、仕様として正式に導入されても、しばらくのあいだはモジュールバンドラーを通して利用することが予想されます。

**Promise.prototype.finally()**

try-catch-finally 句に対応するメソッドです。Node.js の場合、`--harmony_promise_finally` フラグで試すことができます。データベースの接続を閉じる、HTTP リクエストを送信中であることを示すオブジェクトを修正するなどの用途があります。

```js
Promise().then(...)finally(() => {
  $("#animation").hide();
});
```



**Class フィールド**

`#` を使ってプライベートフィールドを宣言できるようになります。

```js
class MyClass {
    static foo = 0;
    #bar;
    constructor(bar) {
        this.#bar = bar;
    }
}
```

## W3C

**EventTarget.addEventListener の第3引数**

`addEventListener` の第3引数が追加され、一度だけ実行する `once` とスクロールの速度改善を目的とした `passive` が追加されました。

```js
element.addEventListener('click', myClickHandler, {
  once: true,
  passive: true,
  capture: true
});
```

**Payment Request API**

決済のためのブラウザーで用意されている UI です。サイトごとに個別に UI を開発する必要がなくなります。

```js
const request = new PaymentRequest(...);
request.show().then(paymentResponse => {
  paymentResponse.complete("success").then(...);
}).catch(...);
```

決済を中止するための `abort` メソッドが用意されています。

```js
request.abort().then(...).catch(...);
```

**User Timing API**

パフォーマンスを計測するための主要なブラウザーは実装済みで Node.js は v8.5 から利用できます。

```js
const { performance } = require('perf_hooks');
performance.mark('A');
console.log('hello world');
performance.mark('B');
performance.measure('A to B', 'A', 'B');
const measure = performance.getEntriesByName('A to B')[0];
console.log(measure.duration);
```

## WHATWG

**URLSearchParams**

URL Sandard で定義されます。ブラウザーだけでなく Node.js も実装しています。Fetch API のリクエストボディに使うことができます。OAuth の認証のように `application/x-www-form-urlencoded` が必須の Web API に対して使うことができます。

```js
const params = new URLSearchParams();
params.append("msg", "hello world");

const opt = {
  method: "POST",
  body: params
};

fetch(url, opt).then(...);
```

**AbortController**

Fetch API のキャンセルを実現するために DOM Standard に導入されました。`AbortSignal` オブジェクトを fetch の第2引数に渡し、`abort()` メソッドでキャンセルを実行します。

```js
const controller = new AbortController();
const signal = controller.signal;

fetch(url, {signal})
  .catch((err) => {
    if (err.name == 'AbortError') {  ... }
  });

controller.abort();
```

AbortController が導入される前に議論された Cancelable Promise の実装の解説は[こちら](https://medium.com/@benlesh/promise-cancellation-is-dead-long-live-promise-cancellation-c6601f1f5082)の記事をご参照ください。

Cancellation API (CancellationTokenSource と CancellationToken、参照実装は prex) は2017年9月時点でステージ0の状態です。

**EventTarget.addEventListener の第3引数**

一回限定のハンドラ実行やブラウザースクロールの速度改善を目的とした passive オプションが追加されています。

```js
element.addEventListener('some-event', eventHandler, {
  once: true,
  passive: true,
  capture: true
});
```

**Streams API**

Chrome や Firefox が Streams API を実装しています。`ReadableStream` と `WritableStream` はどちらも Promise を返します。中止のために `cancel()` メソッドが用意されています。

Fetch API では `response.body.getReader()` を通して `ReadableStream` が使えます。

```js
fetch(...).then(res => {
  let reader = response.body.getReader();
  return reader.read();
  }).then((result, done) => {
    if (!done) {...}
  });
```

Streams API の活用事例として期待されるのは gRPC です。gRPC は通信方法に HTTP/2 を採用しています。1つのリクエストに対して複数のレスポンスを返したり、複数のリクエストに対して1つのレスポンスを返すことができます。

gRPC クライアントの開発に関して、[Github](https://github.com/grpc/grpc-experiments/issues/159#issuecomment-322069486) で非公開のテストユーザーが募集されています (2017年9月時点)。

## モジュールバンドラー

モジュールバンドラーを使い続ける上で次のようなニーズや課題が挙げられます。

 * ライブラリ開発者のためのビルドシステム
 * 開発サーバーのセットアップ作業の軽減
 * トランスパイルするコード量の削減
 * CSS-in-JS

**ツリーシェイキング (Tree-shaking)**

実際に使われていないコードを削除することです。ES2015 モジュールが前提となります。

### rollup.js

rollup.js はモジュールバンドラーです。ES モジュールにデフォルトで対応しています。React、Vue などさまざまな有名ライブラリのビルドツールとして採用されています。

```bash
# ブラウザー
rollup main.js --o bundle.js --f iife

# Node.js
$ rollup main.js --o bundle.js --f cjs
```

Rollup 開発者の[解説](http://postd.cc/webpack-and-rollup-the-same-but-different/)によると、Webpack がアプリケーション開発に向いているのに対して、rollup.js はライブラリ開発に向いているとのことです。

rollup.js を採用した開発サーバーの事例として Felt が挙げられます。


### browserify

**browserify の組織化**

substack 氏の個人プロジェクトから分業体制への移行が進められています。[イシュー](https://github.com/browserify/discuss/issues/5)のページで手助けしてくれる人を募っています。いろいろなプラグインの情報収集ページとして便利です。

**ES モジュールの対応**

主要ブラウザーおよび　Node.js が ES モジュールをサポートするようになったことから、ES モジュールのデフォルトサポートを[求める声](https://github.com/browserify/browserify/issues/1761)が出るようになっています。2017年9月の時点で表立った動きは見られませんが、今後の動向が注目されます。

**budo、bankai**

mattdesl/budo は browserify による開発サーバーです。LiveReload や HTTPS に対応しています。別の選択肢として choojs/bankai が挙げられます。budo は自己署名証明書を自動生成してくれます。

```bash
budo index.js --ssl --cors
```

### Fusebox

Fusebox は TypeScript で開発されたモジュールバンドラーです。開発サーバーやテストランナーが組み込まれています。

```bash
node fuse.js
```

```js:node.js
const { FuseBox } = require("fuse-box");
const fuse = FuseBox.init({
    homeDir: "src",
    output: "dist/$name.js",
});
fuse.bundle("app")
    .instructions(`>index.ts`);

fuse.run();
```

### トランスパイラ (Babel)

**babel-preset-env**

公式で推奨されるプリセットです。設定ファイルでターゲットとして指定したブラウザーや Node.js のバージョンに合わせてトランスパイルするコードの量を減らしてくれます。
`BABEL_ENV` と組み合わせれば、ブラウザーごとに最適化されたコードを生成することができます。

**fast-async**

async/await の速度改善を目的とした Babel プラグインです。ジェネレータを使わないことで、モバイルブラウザーでは10倍の速度改善を実現できるとのことです。

**TypeScript**

Babel 7 でプラグインおよびプレセットが利用できることが予定されています。

  * babel-plugin-syntax-typescript
  * babel-plugin-transform-typescript
  * babel-preset-typescript


### JS パーサー
**Acorn**

Webpack、Rollup、FuseBox に採用されています。v4.0 から async/await に対応しています。プラグインを導入して機能を拡張することができます。dynamic import 対応は仕様のステージが4になってから始まる[予定](https://github.com/ternjs/acorn/issues/477)です。

**Babylon**
Babel に採用されています。Acorn にインスパイアされています。2017年9月時点ではプラグインのための API を公開する予定はなく、フォークする必要があります。

## アーキテクチャ
**ヘッドレス CMS**
フロントエンドとバックエンドを分離した CMS です。フロントエンドは　API を通して構築されます。大きな取り組みの事例は Drupal です。

**PWA (Progressive Web App**)

PWA で何が実現できるのかのチェックリストは Google のサイトで[公開](Progressive Web App Checklist)されています。PWA (Progressive Web App) のためのツール開発は急ピッチで進んでおり、[awesome-pwa](https://github.com/hemanth/awesome-pwa) にまとめられています。


## クライアントアプリ
### 仮想 DOM
**React の特許条項の問題**

React に含まれる Facebook 独自の特許条項が Apache 財団が採用できないライセンスとして認定され、オープンソースのコミュニティから採用を避けるべきツールとして認識が広がっています。Vue.js や React 互換の preact や inferno が注目を集めています。これらの仮想 DOM ライブラリと React の互換性を保つためのレイヤーを追加する preact-compat、inferno-compat も開発されています。

ライセンスの問題だけでなく、仮想 DOM ライブラリのあいだではファイルサイズ削減や処理速度の改善を巡る競争が繰り広げられています。

**タグつきテンプレートリテラル**

タグつきテンプレートリテラル (タグ関数) をサポートする仮想 DOM ライブラリが少しずつ増えています。JSX と比べた利点は ECMAScript の標準であるので、トランスパイラに依存しないことです。

 * snabby - Snabbdom (Vue.js、Cycle.js が採用) をサポートします
 * hyperx -  virtual-dom、react をサポートします
 * hyperHTML - タグつきテンプレートリテラルをネイティブにサポートする仮想 DOM ライブラリです

hyperHTML のサンプルコードを示します。

```bash
function tick(render) {
  render`
    <div>
      <h1>Hello, world!</h1>
      <h2>It is ${new Date().toLocaleTimeString()}.</h2>
    </div>
  `;
}
setInterval(tick, 1000,
  hyperHTML(document.getElementById('root'))
);
```

**asm-dom**

asm-dom は WebAssembly な仮想 DOM ライブラリです。C++ で開発されています。2017年9月の時点で開発初期の段階にあり、snabbdom とのベンチマークでは大きな成果は出せていないとのことです。

### 状態管理
**Repatch**

Repatch は Redux を簡略化させることを目的としたライブラリで、アクションを手軽につくることができます。async/await にも対応しています。


## テスト

**ヘッドレスブラウザー**
Chrome、Firefox からヘッドレスモードが使えるようになり、歴史的な役割を終えた PhantomJS からの乗り換えが進んでいます。Chrome の開発者が Node.js による操作のために Puppeteer を公開しています。テストの用途だけでなく HTML から pdf を生成するためのツールとしても期待されています。

```js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://news.ycombinator.com', {waitUntil: 'networkidle'});
  await page.pdf({path: 'hn.pdf', format: 'A4'});

  browser.close();
})();
```

ヘッドレス Chrome は REPL モードで使うことができます。

```bash
chrome-browser  --disable-gpu --headless --repl
```

macOS の場合、2017年9月の時点で　sudo 権限で実行する必要があります。またカーソルを左右に移動させることはできませんでした。

```bash
sudo /Applications/Google\ http://Chrome.app/Contents/MacOS/Google …\ Chrome --disable-gpu --headless --repl
```