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

  • 88
    いいね
  • 0
    コメント

概要

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

ES モジュール

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

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

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

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

<!-- 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()

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

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 の書き方をする人も見られます。

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 という名前の新しいプロジェクトをはじめたことが注目されました。

ECMAScript 2018

2ality にまとめ記事 があります。アクティブな提案は tc39/proposals にまとまっています。

dynamic import()

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

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 リクエストを送信中であることを示すオブジェクトを修正するなどの用途があります。

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

Class フィールド

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

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

W3C

EventTarget.addEventListener の第3引数

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

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

Payment Request API

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

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

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

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

User Timing API

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

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 に対して使うことができます。

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() メソッドでキャンセルを実行します。

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

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

controller.abort();

AbortController が導入される前に議論された Cancelable Promise の実装の解説はこちらの記事をご参照ください。

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

EventTarget.addEventListener の第3引数

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

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

Streams API

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

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

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 で非公開のテストユーザーが募集されています (2017年9月時点)。

モジュールバンドラー

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

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

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

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

rollup.js

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

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

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

Rollup 開発者の解説によると、Webpack がアプリケーション開発に向いているのに対して、rollup.js はライブラリ開発に向いているとのことです。

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

browserify

browserify の組織化

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

ES モジュールの対応

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

budo、bankai

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

budo index.js --ssl --cors

Fusebox

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

node fuse.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になってから始まる予定です。

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

クライアントアプリ

仮想 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 のサンプルコードを示します。

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 にも対応しています。

PWA (Progressive Web App)

PWA

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

Service Workers
Chrome は Service Workers を実装済みで Safari も開発中です。Workbox はオフラインサイト構築のためのビルドツールを提供します。

workbox generate:sw

テスト

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

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();
})();