abcdefGets

ゲッツ!

TypeScript 2.6 変更点と注意点

TypeScript2.6が出たので変更点を記載
RCからほぼ変更点がない。

Strict Function Typeフラグの導入

--strictFunctionTypesというフラグが導入される。
このフラグは--strictフラグに内包されており、--strictの場合は自動でONになるが、
--strictFunctionTypesfalseにすることで個別にOFFにすることもできる。

動作

関数の引数に対するVarianceの動作を変更する。
TypeScriptの関数のVarianceについては以前下のスライドで説明したので参照。

speakerdeck.com

今回の--strictFunctionTypesフラグがONになると、関数がBivariantではなくてContravariantになる。
つまり以下のような代入が許可されなくなる。

class Animal {
  ...
}

class Dog extends Animal {
  ...
}

declare let acceptAnimal: (animal: Animal) => void;
declare let acceptDog: (dog: Dog) => void;

acceptAnimal = acceptDog // Error
acceptDog = acceptAnimal // OK!

またこの変換が不可能になると、以下のように総称型の代入規則が変化する。

class Animal {
  ...
}

class Dog extends Animal {
  ...
}


class GenericAnimal<T> {
  someFn(animal: T) {}
}


delcare let animalAnimal: GenericAnimal<Animal>;
delcare let animalDog: GenericAnimal<Dog>;

animalAnimal = animalDog // Error.
animalDog = animalAnimal // OK.
// someFn(animal: Dog)
// someFn(animal: Animal)

これは関数がBivariantではなくなるために起こる。
Animalを受け取る関数にDogを受け取る関数を渡すと、DogAnimalの派生型である限り、
Dog固有の処理を行っていた場合にRuntimeエラーを起こす可能性が高く危険な代入になるが、
Animalを受け取る関数にDogを渡すのは安全であるため、このような変換規則になる。
そのため外から見るとDog <= Animalの変換をしているように見えるが、関数の引数という文脈で考えると、
この変換はAnimal <= Dogの変換となり自然に見える。

注意

--strictモードでこの機能が有効になるため、--strictオプションをONにしているコードに対しては、
この変換規則が強制的に適用される。
なので今まで逆の変換を行っているコードはすべてコンパイルエラーになる。
それが困る場合は--strictFunctionTypes falseでOFFにすることができる。

タグ付きテンプレートリテラルをキャッシュするようになった

タイトル通り。 一度作ったテンプレートリテラルオブジェクトをキャッシュするようになったので、以下のコードがtrueになるようになった。

export function id(x: TemplateStringsArray) {
  return x;
}

export function templateObjectFactory() {
  return id`hello world`;
}

let result = templateObjectFactory() === templateObjectFactory(); // true in TS 2.6

Emit Result in v2.5

"use strict";
exports.__esModule = true;
function id(x) {
    return x;
}
exports.id = id;
function templateObjectFactory() {
    return (_a = ["hello world"], _a.raw = ["hello world"], id(_a));
    var _a;
}
exports.templateObjectFactory = templateObjectFactory;
var result = templateObjectFactory() === templateObjectFactory(); // true in TS 2.6

Emit Result in v2.6

"use strict";
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
    if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
    return cooked;
};

function id(x) {
    return x;
}

var _a;
function templateObjectFactory() {
    return id(_a || (_a = __makeTemplateObject(["hello world"], ["hello world"])));
}

var result = templateObjectFactory() === templateObjectFactory();

ここに注目

// v2.5
function templateObjectFactory() {
    return (_a = ["hello world"], _a.raw = ["hello world"], id(_a));
    var _a;
}

// v2.6
var _a;
function templateObjectFactory() {
    return id(_a || (_a = __makeTemplateObject(["hello world"], ["hello world"])));
}

変数_aに生成済みテンプレートオブジェクトをバインドして再利用しているのがわかる。

コンパイルエラーメッセージ・コマンドラインヘルプが多言語化

localeに合わせて各種言語ファイルが使用されるようになった。

// @ts-ignoreコメントでjsファイルのエラーを抑制できるようになった。

--allowJSオプションを使ってJSファイルをロードした際に出るコンパイルエラーを、

if (false) {
  // @ts-ignore: Unreachable code error
  console.log("hello");
}

のような形で指定することで抑制できる。

--watchモードが高速化

--watch機能をすべて書き直して高速化した。 プロジェクト全体をコンパイルするのではなく、変化したファイルのみを処理することで処理速度が向上した。

WriteOnlyな値をunusedとして処理するように

TypeScript2.6では、--noUnusedLocals--noUnusedParametersオプションを刷新した。
値が宣言されているか書き込まれていても、その値が読み出されない限りunusedとしてマークされる。

function test(unused1: number) {
  unused1 = 0;
}

class Test {
  private unused2: number;
  constructor() {
    this.unused2 = 0;
  }
}

上記のサンプルコードではunused1unused2はともに書き込みはあるものの、
読み出しがないのでunusedとしてエラーになるので注意。

追記

LanguageService系の更新書くの忘れてた

implicit anyのQuick fixを追加

implicit anyが見つかった場合にnoImplicitAnyがtrueならQuickFixで推測した型を提案する。

JSDocをTypeScriptの型アノテーションに変換する

JSDocに書いた型注釈をTypeScriptの型に変換できるようになった。

呼び出しが必要なDecoratorにWarningを出すようになった

高階関数型の呼び出しが必要なDecoratorを呼び出し無しで使った場合に、
呼び出しを提案する。

@typesから自動でinstall

型定義がない場合に@typesから自動でインストールを提案する。

Ambient宣言内のdefault exportで式が使えなくなった

タイトルの通り

Intersection Typeの結果を変更

number & string, "foo" & 42,

のような不可能な型はnever型を返すように

lib.d.ts変更

Deprecation

getSymbolDisplayBuilderAPIが廃止になった。2.7で削除予定
基本的には代わりにTypeChecker#symbolToStringを使う。
もっと入り組んだユースケースの場合にはバージョン2.7でsymbolToStringを導入予定なのでそれを使う。

まとめ

今回はちょっと破壊的変更がいくつかあるので注意。
特に--strictをONにしている人は総称型のアップキャストをしていないかをチェックした方がいい。

Ecmascriptのprotocolについて

Ecmascriptにprotocolを実装するという提案がある。

proposal-first-class-protocol

もともとGotanda.jsで発表した内容だけどいろいろ追記した。
資料はこれ

speakerdeck.com

内容

元々はinterfaceの提案だった。
それが名前と形を変えてprotocolの提案になった。

protocolとは?

classが実装すべき規約。
interfaceと違って型ではなく規約を提供する。
型クラスとかが近いのかな?

Ecmascript protocol

基本

今回の提案はちょっと特殊な形のprotocolの実装で、
実態はSymbolのコレクションになっている。

例.

protocol ProtocolName {
  // 実装が必要なシンボルを宣言
  thisMustBeImplemented;
}
  
class ClassName implements ProtocolName {
  [ProtocolName.thisMustBeImplemented]() {
    ...
  }
} 

上の実装を見るとわかるけど、protocolのメンバーは自動的にSymbolになる。
そしてprotocolを実装するクラスはそのSymbolを実装しなければいけない。
さらにprotocolは複数実装できる。

例.

protocol A { a; }
protocol B { b; }
  
class ClassName implements A, B {
  [A.a]() {}
  [B.b]() {}
}

またprotocolは実装を持つこともできる。

例.

protocol ProtocolName {
  // 実装が必要なシンボルを宣言
  thisMustBeImplemented;
  
  // メソッド実装
  youGetThisMethodForFree() {
    return this[ProtocolName.thisMustBeImplemented]();
  }
}
  
class ClassName implements ProtocolName {
  [ProtocolName.thisMustBeImplemented]() {
    ...
  }
}

const className = new ClassName();
className.youGetThisMethodForFree(); // ClassNameのインスタンスで呼べる

このprotocolは実装を持っていて、それを実装したクラスは同時に実装も引き継ぐ。
なのでtraitの様に振る舞うことができる。

拡張

さらに既存のクラスも拡張することができる。
たとえばArrayを拡張する場合。

protocol Functor {
  map;
}
  
Promise.prototype[Functor.map] = () => {
  ...
}

// このimplement関数でFunctorを実装したことを宣言
Protocol.implement(Promise, Functor);

このように既存クラスのprototypeを拡張し、implement関数を呼び出すことで、
既存のクラスにもprotocolを適用することができる。

チェック

あるクラスがprotocolを実装しているかどうかは、instanceofでは判定できない。
なので、implements演算子が提案されている。

Promise implements Functor // true

if (MyClass implements SomeProtocol) { }

上記の例のようにimplementsキーワードが演算子のように振る舞う。

extends

protocol自体は既存のprotocolを拡張することができる。

例.

protocol A { a; }
protocol B { b; }
protocol C extends A, B { c; }

こんな感じでprotocolは複数のprotocolを拡張することができる。

デフォルト実装

上記の拡張を利用するとデフォルト実装を持つprotocolを定義することができる。

例.

protocol A { a; }

protocol B extends A {
  [A.a]() { ... }
}

class Class implements B {}
const c = new Class();
c[A.a]();

protocol Bはprotocol Aを拡張し更にaを実装している。
なので、Classインスタンスに対してA.aを呼び出すことが可能になる。

static protocol

protocolはなんと静的プロパティ(!)に対しても作用させられる。

例.

protocol A {
  static b() {}
}
  
class C implements A { }
C[A.b]();

これはどうなんだ...

TypeScript

とりあえず既存のinterfaceは残すであろうとのこと。
protocolをどのような型として扱うかは未定。
Allow dynamic name in typesという型のプロパティ名にsymbolを使うことができるPRが進んでいるので、
これが実装されるとprotocolもスムーズにいけるかも。
ちなみにここで議論しています。

Question: TypeScript and proposal-first-class-protocols · Issue #18814 · Microsoft/TypeScript · GitHub

prototypeへの直接代入について

先程、既存のクラスにprotocolを実装するケースでprototypeへ直接代入するという例を出したが、
prototypeへの直接代入は何か気持ち悪い...(Object.definePropertyすらない時代からjsを書いていたせいなのか?)ので
それはやめてくれ!その代わり既存のクラスをopen-classにして拡張できるようにすれば良くない?
というアバウトな提案をしてみた。

protocol Functor {
  map;
}
  
// We can implements protocol after declaration.
Array implements Functor {
  [Functor.map]: Array.prototype.map
}

こんな感じ。

結果

とりあえず、classがprototypeを隠蔽したのにこれじゃES5時代じゃないか!
って思いを伝えたら。

classはprototypeを隠蔽したわけじゃないよ。prototypeへの代入もES5時代的ではない。
Ecmascriptのモンキーパッチ方法は常にprototypeだし、これからもjsは未来永劫変わらずこの方法なのさ。

とこのような調子で言われた。
まあEcmascriptがprototypeを隠蔽して、もっと 普通 のclassベース言語になりたがってるように見えていた私にとって、
これが目指す世界ならもう言うことは無かったので議論を打ち切った。

でもprototypeはもう触りたくないんじゃ!

おしまい。

HTML5 Conference 2017に登壇します

ひょんなことから

HTML5 Conference

に登壇する機会をいただけることに。

タイトルはDeep dive into TypeScriptということで、TypeScriptの紹介や、RoadMap・Issuesの話とかをさせて頂くつもり

同じ時間帯に任天堂の方のセッションとかあってアレなんですが、TypeScriptに興味のある方はぜひ。

Typescript 2.5 リリース

Typescript 2.5がでたので変更点をメモ

Optional-catch-bindingの導入

catch節のエラーオブジェクトが省略可能になった。
現在Stage3のOptional catch bindingが導入された形に。

サンプル

let input = "...";
try {
    JSON.parse(input);
}
catch {
    // ^ catch節の変数宣言を省略している。
    console.log("Invalid JSON given\n\n" + input)
}

jsにJSDocベースの型アサーションとキャストを導入

javascriptファイルもJSDocコメントで型チェック可能になった。
コメントで@ts-checkをつけるか、
コンパイラオプションでcheckJsオプションをtrueにすれば動作する。

型チェックの詳細は以下のWikiに書いてある。

Type Checking JavaScript Files · Microsoft/TypeScript Wiki · GitHub

重複したパッケージのリダイレクト

簡単に言うとnode_modulesのパッケージの同一性チェックをする。

つまり、moduleResolutionがnodeの場合node_modulesからモジュールをインポートするが、
その際にそのモジュールのpackage.jsonをチェックして、すでにインポートした事のあるモジュールかどうか確認する。
もし同一のモジュールであれば、すでにimport済みのモジュールにリダイレクトして同じモジュールをロードしない。
ちなみにpackage.jsonのversionとnameをチェックして同一性判定をする。

node_modules -
             |
             [A-package] -
             |          |
             |          node_modules -
             |                       |
             |                       [C-package]
             [B-package] -
                         |
                         node_modules -
                                      |
                                      [C-package]

この例だと、A-packageとB-packageが同じC-pacakgeを必要としているが、typescriptコンパイラは一度しかC-packageをインポートしない。
B-packageの必要とするC-pacakgeはA-packageのC-packageを参照する。

–preserveSymlinksコンパイラフラグの導入

–preserveSymlinksオプションが導入される。
このオプションはシンボリックリンクされたモジュールが他のモジュールをimportする場合に、
シンボリックリンク元のパスを起点とした相対パスではなく、
シンボリックリンクが置かれている場所からの相対パスとなる。

A -
  |
  B -
  |  |
  |  C'(Symblink of C)
  C

この例だと、C'モジュールのパス解決は–preserveSymlinksをセットしない場合は、
A/Cから始まることになる。
しかし–preserveSymlinksをセットすると、
A/B/C'からパス解決が行われる。

まとめ

意外と新機能は少なかったですね。

typescript 2.4 の新機能

typescript2.4がでたので新機能を確認。

Dynamic Import Expressionsのサポート

import('...')式がサポートされた。

import式を使うことで多くのバンドラーがコード分割をすることが可能になるので、
module: esnextで出力するのがおすすめだそう。

async function getZipFile(name: string, files: File[]): Promise<File> {
  const zipUtil = await import('./utils/create-zip-file');
  const zipContents = await zipUtil.getContentAsBlob(files);
  return new File(zipContents, name);
}

String Enumsのサポート

待望?の文字列enumがサポートされた。

enum Colors {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE",
}

ただし制限として、数値enumの際に可能だった、メンバプロパティからプロパティ名の取得はできない。

Colors[Colors.Red] // これはできない。

interfaceのgeneric型のサポートを強化

戻り値の推論能力の強化

戻り値から型パラメータを導出できるようになった。

function arrayMap<T, U>(f: (x: T) => U): (a: T[]) => U[] {
  return a => a.map(f);
}

const lengths: (a: string[]) => number[] = arrayMap(s => s.length);

Promiseもこのようにエラーとすることができるように。

let x: Promise<string> = new Promise(resolve => {
  resolve(10);
  //      ~~ Error!
});

文脈からの型パラメータの導出

いかのような定義があった場合

let f: <T>(x: T) => T = y => y;

yanyになってしまっていた。
そのため、

let f: <T>(x: T) => T = y => y() + y.foo.bar;

のような式は型チェックをスルーしてしまっていたが、
2.4からyが正しく導出され、エラーとなるようになった。

generic関数の方チェックを厳格化

以下のような関数があった場合、

type A = <T, U>(x: T, y: U) => [T, U];
type B = <S>(x: S, y: S) => [S, S];

function f(a: A, b: B) {
  a = b;  // Error
  b = a;  // Ok
}

AがBと互換性があるかをチェックできるようになり、互換性があれば、代入も可能になった。

コールバック関数の方の反変性チェックを厳格化

2.4以前のバージョンでは以下のプログラムではエラーが起きなかった。

interface Mappable<T> {
  map<U>(f: (x: T) => U): Mappable<U>;
}

declare let a: Mappable<number>;
declare let b: Mappable<string | number>;

a = b;
b = a;

なぜなら、abがmap関数のパラメータfを通して変換可能であると判断されていたため。
しかし2.4以降は実際のabの型を比較を行うため、このプログラムはコンパイルエラーとなる。
この変更は破壊的変更となるので注意

Weak Typesの導入

以下のように、すべてがoptionalな型をWeakTypeとして区別することになった。

interface Options {
  data?: string,
  timeout?: number,
  maxRetries?: number,
}

2.4からはこのWeakTypeに対しても、存在しないプロパティを持つ型を代入するとエラーになる。

function sendMessage(options: Options) {
  // ...
}

const opts = {
  payload: "hello world!",
  retryOnFail: true,
}

// Error!
sendMessage(opts);
// optsとOptions型で一致するプロパティがないためエラー

この変更は破壊的変更となるので注意

現在以下のWorkaroundが提案されている。

  • 実際にプロパティが存在する場合のみ宣言する。
  • WeakTypeには{[propName: string]: {}}のようなインデックス型を定義する。
  • opts as Optionsのように型アサーションを使って変換する。

まとめ

型チェックの強化がメインの変更点になった。
破壊的変更が幾つかありますが、WeakTypeのとこはちょっと注意した方が良さそう。
あとはimport式をどう使うか。

babelのAsyncIterationバグ

問題

for-await-ofのボディで配列への分割代入を行うと、

Cannot read property 'file' of undefined

というエラーを投げてトランスパイルに失敗する。
どうやらscopeの解析に失敗しているらしい。

サンプルコード

async function g(t) {
  return new Promise(r => setTimeout(() => r([true]), t));
}

async function r() {
  for await (const t of [1000, 2000, 3000]) {
    const [result] = await g(t);
  }
}

r();

package.json

"dependencies": {
  "babel-cli": "^6.24.1",
  "babel-plugin-transform-async-generator-functions": "^6.24.1",
  "babel-plugin-transform-regenerator": "^6.24.1",
  "babel-plugin-transform-runtime": "^6.23.0",
  "babel-polyfill": "^6.23.0",
  "babel-preset-es2015": "^6.22.0",
  "babel-preset-stage-3": "^6.24.1",
  "babel-runtime": "^6.23.0"
},
"babel": {
  "plugins": [
    [
      "transform-runtime",
      {
        "helpers": false,
        "polyfill": false,
        "regenerator": true,
        "moduleName": "babel-runtime"
      }
    ]
  ],
  "presets": [
    "es2015",
    "stage-3"
  ]
}

解決

とりあえずbugとしてbabel側にはissueを立てておいた。

github.com

現状で取れる選択肢

配列ではなくオブジェクト形式で受け取る

データ構造の変更が必要だがまあ許容できるか?

async function g(t) {
  return new Promise(r => setTimeout(() => r({result: true}), t));
}

async function r() {
  for await (const t of [1000, 2000, 3000]) {
    const {result} = await g(t);
  }
}

r();

分割代入で受け取らない

その後にインデックスアクセスしなければならないのでちょっと面倒

async function g(t) {
  return new Promise(r => setTimeout(() => r([true]), t));
}

async function r() {
  for await (const t of [1000, 2000, 3000]) {
    const result = await g(t);
  }
}

r();

まとめ

はやく直ってくれると嬉しい

Webpackでpolyfillをちゃんと動かす

面倒だったので備忘録

Polyfillの設定

とりあえずwebpackはrequireしないと駄目。
でも既存のコードを一切変えずにpolyfillをインストールしたい。

webpack.ProvidePluginを使う。

new webpack.ProvidePlugin({
  'Promise': 'es6-promise',
  'Symbol': 'es6-symbol',
  'fetch': 'imports?this=>global!exports?global.fetch?window.fetch!whatwg-fetch',
  'Response': 'imports-loader?this=>global!exports-loader?global.Response!whatwg-fetch'
}),,

こんな感じで書いた。

次に

npm i imports-loader exports-loader -Dを実行。

Promise、Symbolはrequire('es6-promise')require('es6-symbol')がそれぞれPromiseSymbolという変数名でモジュール毎に定義される。
fetchはとても読みづらいし考えたやつ頭おかしいが、
まず、whatwg-fetchの定義の下のthisglobalを参照している箇所でmodule.exports = fetchgを定義して、
fetchを参照しているモジュールに(function(fetch){ ... })(require('whatwg-fetch'))のようなコードを追加する。
Responseも同じく。

まとめ

webpackの独自仕様を追加すぎるとESModuleへの移行がつらそうなので程々に。