メモを揉め

お勉強の覚書。

Flowtype 導入メモ

f:id:alluser:20170821031214p:plain結構な数のつまづきがあったのでメモしておきます。
なお、Flow は現在も活発に開発中のため、そこそこカジュアルにBreaking Changeする可能性があります。
この記事はあくまでつまづきやすかったポイントを残すにとどめているので、基本は公式のドキュメントを参考にすることをお勧めします。

つまづきポイント

  • Nuclide の設定がややこしい
  • Flow サーバーがそこそこ不安定
  • Flowのコードは2種類ある
  • FlowのエラーとESLintのエラーがある
  • class-fields っぽい書き方とオブジェクトリテラルっぽい書き方がある
  • ドキュメントやエラーメッセージに出て来る英語が難しい
  • ドキュメントの擬似コード
  • 正確な型を表現することの難しさ

一つ一つの難易度はそれほど高くなくても、二つ以上のつまづきが同時に起こる事で対処が難しくなる。
問題を切り分けるのが大事。

Nuclide の設定がややこしい

とにかく設定項目が多いので必要ない機能は全部切る。
この記事を参考に設定しました。

Nuclide というのは Atomプラグインで、Facebook スタックの開発環境をカバーする全部入りの IDE 的なもの(だと思っている)。
Flow 以外にも Hack、Swift などのサポートもある。1

config.cson を置いときます、Swift 関連も有効にしてる。

その他のエディタサポートについてはこんなものがあるようです。

Flow サーバーがそこそこ不安定

Flow がコードを検証する時、毎回全てのコードを読み込むわけじゃなくて、裏でサーバーを立ててキャッシュし、二回目以降のチェックではそれを利用するような仕組みになっているらしい。

注意点として、Flowは型情報の収集にバックグラウンドでサーバを走らせるのだが、.flowconfig をファイルを書き換えた際の検知がそこそこの確率で失敗している。困ったら flow stop; flow と叩いていることが多い。 flow restart もうまく動いていないことが多い。経験上。

とくに、次の型定義ファイルをいれたときに、更新が設定ファイルのリロードに依存していて、結構頻繁にこれを叩くことになる。

flow-の設定変更と再起動

これホントその通りで、感じが掴めるまではサーバーが変更後のコードを検証してくれてるかどうか注意したほうがいい。
やってるうちにだんだんサーバーの気持ちが分かるようになってくるという謎の技術でカバーするのがまた良くない。

一番確実なのは flow check を実行すること。 経験上。

Flowのコードは2種類ある

通常の JS コードに型注釈を付けていく、いわゆる普通のシンタックスの他に、declare キーワードを使ったライブラリの型定義のためのシンタックスがある。

少しややこしいのは、この declare キーワードを使ったシンタックスは、通常のコードに混ぜて使うことも出来る、という点。
しかもこの機能、ドキュメントからいつの間にか消えているので、なぜエラーにならないのかもはや知る術がない。2
何かの拍子に書いた際にエラーにならなくて、エディタの動作を疑ったりした事があったので、とりあえずこういうシンタックスがあるということを知っておく。慌てない。

FlowのエラーとESLintのエラーがある

自分は Atom の Nuculide と linter-eslint を使ってそれぞれエラーがあればエディタ内に表示されるようにして使っている。

ここで気を付けないといけないのはエラーを出しているのが Flow なのか ESLint なのかを意識するということ。
具体的には ESLint のこのエラーの時に Flow のエラーだと勘違いしてハマった。

そんなところでつまづかねえよと思われるかもしれないが、Flow のエラーを修正してる途中でポッと ESLint のエラーを吐かれたりすると、頭が Flow の世界に入ってしまっているので、なぜエラーになるのか分からずしばらく悩んでしまった。
もちろん、エラーメッセージをちゃんと読めっていう話なんだけども。

これに加えて ESLint に Flow 向けのプラグインとルールを設定しておくと良い。

最近本体にも Lint の機能が入ったのでこれも設定するとなお良い。

Class Fields っぽい書き方とオブジェクトリテラルっぽい書き方がある

Class Fileds は class ブロック直下でプロパティを初期化できるというもの。

Object Types と Interface Types の定義の時に、各プロパティーのデリミタを , にするか ; にするかという問題。

結論から言うとどちらも同じだそうです。ただし Object Types については、セミコロンは後方互換性のためでカンマ推奨。
Interface Types についてはよく分かんないっす。class の interface なのか、単純な map の interface なのか文脈で使い分ければ良いのかな。

Note: Previously object types used semicolons ; for splitting name-value pairs. While the syntax is still valid, you should use commas ,.

Object Types

以下のように4つの型全てに互換性がある。

type SemicolonObject = {
  foo: number;
  bar: boolean;
  baz: string;
};

type CommaObject = {
  foo: number,
  bar: boolean,
  baz: string,
};

interface SemicolonInterface {
  foo: number;
  bar: boolean;
  baz: string;
}

interface CommaInterface {
  foo: number,
  bar: boolean,
  baz: string,
}

const so: SemicolonObject = {
  foo: 1,
  bar: true,
  baz: 'three',
};
const si: SemicolonInterface = so; // OK
const co: CommaObject = si; // OK
const ci: CommaInterface = co; // OK

じゃあ Interface と Object の違いは何かという話なんだけど、ドキュメントを見るとまあいろいろ違う。
自分は以下のように使い分けてる。

  • Interface Types
    • class の interface を定義したい
    • extends したい
    • static field 使いたい
  • Object Types
    • それ以外の時

ドキュメントやエラーメッセージに出て来る英語が難しい

前提として自分は英語が得意じゃないです。平易な英語なら読めるけどちょっと難しいのだと解釈が合ってるか自信ない。
あとは、型についての知識とか専門用語についても Flow を触り始めてから学んだことが多い。
そのせいも大いにあるが、ドキュメントやエラーメッセージに出てくる単語や言い回しは見慣れないものが多く、独特の雰囲気を感じる。

たとえば Lint Rule の中の一つで sketchy-null というのがあるんだけど、どういうルールなのかパッと連想できるだろうか。

これは if (value) { /* ... */ } のような甘い null チェックを指摘してくれるルールなんだけど、sketchy っていう表現は最初なんだか分からなかった。
英語分かる人にとってはむしろ直感的だったり、気の利いた言い回しだったりするんだろうなとは思う。

それから、専門用語。
静的型付き言語の世界では一般的な言葉なのかもしれないけど、variance、variant、covariant、invariant、contravariant みたいな言葉がドキュメントとかエラーメッセージでガンガン出てくる。

英語の壁と型の壁、二重の壁が立ちはだかる。というと大げさかもしれないが、チームで導入するならそういう心理的障壁は考慮する必要があるかもしれない。

ドキュメントの擬似コード

Flow のドキュメントは Flow が型をどう扱うか、ということの説明に結構多くの部分を割いている。
それ自体はすごい助かるし勉強になるんだけど、時々説明のために擬似コードを使うことがあって、前後の文脈を読まずにサンプルコードだけを追うと訳が分からなくなる。
以下の場所で使われている。

正確な型を表現することの難しさ

単純なコードの場合はいい。
コードが少しでも複雑になると、自分の頭の中では型が解決できていても Flow がエラーを吐く、という場面がけっこう出てくる。
このエラーを取り除くには、自分の頭の中の型情報を正確に Flow に伝えてあげる必要がある。
型情報を正確に Flow に伝えるには、Flow を用いた型の表現をしっかりと身に着けないといけない。
Flow 自体には高い表現力があるが、それだけにそれを正確に使いこなすのが非常に難しい。
複雑な表現はミスが入り込む余地も大きくなるので、慣れるまでは正確な型情報を一発で書けるということは中々無い。3
中途半端な理解で無理して型を表現しようとすると、型情報が間違っていたりするだけでなく、間違っているかどうかを判断することも難しくなるので、どんどん本筋と関係ないところで時間を奪われることになる。

段階的な導入は可能?

Flowtype の一つのメリットとして、段階的に導入できるという部分が挙げられることが多いが、果たして本当にそうだろうか?
自分が思うに、それはある意味では正しく、ある部分では誤解を与えているような気がする。これはコマーシャル的な意味でおそらく意図的にそうしているのかもしれない。

ここで言う段階的というのは、あくまで Flow を適用する範囲の話であって、学習曲線がなだらかで覚えやすいから導入しやすい、という話では無い。
ダイジョブ、ダイジョブ、使いながらおぼえればいいからではないのだ。

上でも書いたとおり、Flow で思った通りに型を表現出来るようになるまでには結構時間がかかる。
そんな状態で既存コードに型を付けていこうとしても、結構なんもできない。ある程度 Flow に精通して型の表現力を身に付けていないと、ただただ any まみれで何が嬉しいのかさっぱり分からないコードになってしまう。
初めから Flow で書くほうが、知っていることしか書けない分むしろ楽。

導入は段階的でも、学習コストは初めにしっかり払う必要がある。

で Flow どうなの?

上で挙げたようなつまづきを乗り越え、晴れて型の恩恵に預かれるようになった暁には、たしかに Flow の型推論は賢いと感じたし、個人的にはこの先も触っていきたいと感じた。
ただ Breaking Change がまだまだあるかもしれないし、今直ぐギョウミーなコードに導入するのはリスキーかもしれない。
チームで導入するなら、ある程度 Flow の習熟度や型の知識などを揃えられないと現実的じゃないと思ってる。
勉強したり共有したりする時間とメンバーが確保できるならやってみたい気持ちはある。

最近 Angular を触り始めた影響で TypeScript にも入門したが、めちゃくちゃ簡単に導入できたような印象がある。
これはもちろん Flow で先につまづいておいたポイントを回避できた部分も大きいが、VSCodeや圧倒的な型定義ファイルの量(と質?)が物を言ったという感じがする。

TypeScript と Flow は共通点も多いので、片方で得られたノウハウはもう片方でもある程度使えると思う。
好きな方を勉強しておけばいざという時にはそれほど困らないような気がしてる。
それにバージョンアップを重ねるごとに違いも無くなっていってる気がするし。

そんなわけで Flow はそういうことも引っくるめて面白いので、これからも適当に watch していくと思う。

【おまけ】ドキュメントから消えた機能

Mixins

今ではもう使えなくなっている。うそ。まだ使えた。4

// You can mixin more than one class
declare class MyClass extends Child mixins MixinA, MixinB {}
declare class MixinA {
  a: number;
  b: number;
}
// Mixing in MixinB will NOT mix in MixinBase
declare class MixinB extends MixinBase {}
declare class MixinBase {
  c: number;
}
declare class Child extends Base {
  a: string;
  c: string;
}
declare class Base {
  b: string;
}

var c = new MyClass();
(c.a: number); // Both Child and MixinA provide `a`, so MixinA wins
(c.b: number); // The same principle holds for `b`, which Child inherits
(c.c: string); // mixins does not copy inherited properties,
               // so `c` comes from Child

Inline declarations in regular code

Qiita の記事でサンプルコードに使われてたりして、なるほどこういう時には便利だなと思ったことがあった。

declare class List<T> {
  map<U>(f: (x: T) => U): List<U>;
}
declare function foo(n: number): string;

function fooList(ns: List<number>): List<string> {
  return ns.map(foo);
}

  1. 見た感じ Reason サポートは今のところ無さそう。あった Enable the "nuclide-language-reason" feature

  2. 今だと中国語訳のページがまだ残っている。 inlining-declarations-in-regular-code

  3. と思っておいた方が、他人が書いた型情報にも疑ってかかる癖が付いて良い。

  4. こちらも中国語訳のページに残っている。 Mixins

Stylus JavaScript API とプラグインの仕組みについて

f:id:alluser:20161030234249p:plain

先日 foovar という Stylus のライブラリを公開しました。
Stylus の変数を JS ファイルにエクスポートするためのライブラリです。

memowomome.hatenablog.com

その時に調べた Stylus JavaScript API の詳細や、プラグイン作成の方法です。

仕様の変更などにより記事の内容が古くなったらすぐ分かるように、記事のテストを書いてみました。

CircleCI

Stylus プラグインの仕組み

プラグインの実体は関数を返す関数(高階関数)です。
これを module.exports するだけでプラグインの体を成します(役に立たないということを除けば)。
npm で公開すればすぐにプラグインとして利用可能です。

module.exports = function() {
  return function() {};
};

外側の関数は use (後述)した際に呼ばれ、オプションを引数に受取ります。
内側の関数は Rendererインスタンスを引数に受け取ります(コード中stylus)。

module.exports = function(...options) {
  return function(stylus) {};
};

内側の関数内の this はこの Renderer インスタンスに束縛されて呼び出されます。
require('stylus')() で得られるインスタンスも同じく Rendererインスタンスです。

module.exports = function(...options) {
  return function(stylus) {
    this.condtructor.name // 'Renderer'
    this === stylus; // true
    this.constructor === require('stylus')().constructor; // true
  };
};

基本的にはこの Renderer インスタンスを介して Stylus の世界とやり取りをすることになります。
Stylus から呼び出せる関数を JS で定義したり、あらかじめ用意した .styl ファイルへのパスを通したりといった感じです。
この Renderer インスタンスAPI については公式のドキュメントに詳しい情報があります。

プラグインの読み込みとオプションの渡し方

プラグインを読み込む方法は3つあります。

  • ビルトイン関数 use(path) を使う
  • JavaScript API.use(fn) を使う
  • CLI--use name オプションを使う

いずれも use という言葉が使われており、このキーワードがプラグイン読み込みを指すと思われる。

ビルトイン関数 use(path)

JS ファイルのパスを指定して呼び出す。
プラグイン名での呼び出しは出来ない。

第2引数にはハッシュをオプションとして渡せる。 渡せるのはハッシュのみで、それ以外はエラーになる。
また第3引数以降は無視される。

use('./my-plugin.js')
use('../../node_modules/plugin/index.js') // 名前での呼び出しは出来ない。 node_modules 配下のエントリポイントを直接指定することは出来る
use('../../node_modules/plugin/index.js', { foo: 20px }) // オプションに渡せるのはハッシュのみ

JavaScript API.use(fn)

前述の高階関数内側の関数のみを渡す。
require で読み込んだモジュールを実行して内側の関数を取り出し use に渡すという使い方になる。

const stylus = require('stylus');

stylus(source)
  .use(require('plugin-name')())
  .render(/*...*/);

外側の関数にはオプションを渡すことが出来る。
require で読み込んで関数を直接呼び出すだけなので、好きな数の引数を好きな型で渡すことができる。
use(path)CLI--use オプションとAPIを揃えるために、オプションはオブジェクト1個に限定したほうがいいかもしれない。

stylus(source)
  .use(require('plugin-name')({foo, 'bar'}))
  .render(/*...*/);

CLI--use name オプション

--use に続けてプラグイン名を渡すと読み込んでくれる。
短縮形は -u

$ stylus --use plugin-name path/to/source.styl
# あるいは
$ stylus -u plugin-name path/to/source.styl

--with に続けて文字列を渡すと、 JS としてパースされ外側の関数にオプションとして渡される。
なぜかドキュメントに載ってないし、 --help にも出てこない。

この場合複数の引数は指定できないので、複数のオプションを扱いたい場合はオブジェクトや配列で渡す必要がある。

$ stylus -u plugin-name --with '{ foo: true }' path/to/source.styl

Stylus のトークンを表現するクラス

ここで記載する API はごく一部です。
より詳しいクラスの詳細はここらへんを見ると良さそう。

これらのコンストラクタを new して Stylus のトークンを自前で生成することもできます。

トークンクラスへのアクセス

evaluator.renderer.nodes.* で Stylus のトークンクラスにアクセスできる。
evaluatorJavaScript APIdefine(name, fn) で定義した関数内から this で参照することができる。

const fn = function() {
  return new this.renderer.nodes.String('hello world');
};

module.exports = function() {
  return function(renderer) {
    renderer
      .define('hello-world', fn);
  };
};

以下、各クラスのコンストラクタの使い方、プロパティの型、コンソールに出力した結果です。

Expression

式を表すクラス。
nodes 内の要素が複数であれば tuple もしくは list となる。(要素が一つの場合を長さが1の tuple と言うこともできる)
tuplelist の違いは isList で判定できる。

constructor

  • Expression(isList)
    • isList: boolean

instanceMethod

  • push(node): nodethis.nodes に追加する
    • node: node

instanceProperty

  • nodes: array
  • isList: boolean

{
  lineno: 1,
  column: 14,
  filename: 'index.css',
  nodes: [
    {
      lineno: 1,
      column: 14,
      filename: 'index.css',
      val: 'some string',
      string: 'some string',
      prefixed: false,
      quote: '\''
    }
  ],
  isList: undefined
}

Object

Stylus のハッシュを表すクラス。
vals には JS のオブジェクトが入っており、各キーにはさらに Stylus のトークンが入る。

constructor

  • Object()

instanceMethod

  • set(key, value): value の値を key にセットする
    • key: string
    • value: node

instanceProperty

  • vals: object

{
  lineno: 1,
  column: 10,
  filename: 'index.css',
  vals: {
    foo: {
      lineno: 1,
      column: 19,
      filename: 'index.css',
      nodes: [Object],
      isList: undefined
    }
  }
}

String

Stylus の文字列を表すクラス。

constructor

  • String(val, quote)
    • val: string

instanceProperty

  • string: string
  • val: string

{
  lineno: 1,
  column: 14,
  filename: 'index.css',
  val: 'some string',
  string: 'some string',
  prefixed: false,
  quote: '\''
}

Unit

単位付き(あるいは無し)の数値を表すクラス。
10px1.7em200ms0 などは全て Unitインスタンスとなる。

constructor

  • Unit(val, type)
    • val: number
    • type: string

instanceProperty

  • val: number
  • type: string
  • raw: string

{
  lineno: 1,
  column: 10,
  filename: 'index.css',
  val: 20,
  type: 'px',
  raw: '20px'
}

RGBA

RGBA形式の色を表現するクラス。
#112233 , #11223344 , rgb(11,22,33) , rgba(11,22,33,44) などは全て RGBAインスタンスとなる。
16進数表記の時のみ raw プロパティが存在し、16進数表記の文字列が取得できる。

constructor

  • RGBA(r, g, b, a)
    • r: number
    • g: number
    • b: number
    • a: number

instanceProperty

  • r: number
  • g: number
  • b: number
  • a: number
  • raw: string

{
  lineno: 2,
  column: 16,
  filename: 'index.css',
  r: 17,
  g: 34,
  b: 51,
  a: 0.26666666666666666,
  name: '',
  rgba: [Circular],
  raw: '#11223344'
}

HSLA

HSLA形式の色を表現するクラス。
hsl(11,22,33) , hsla(11,22,33,44) などは全て HSLAインスタンスとなる。

constructor

  • HSLA(h, s, l, a)
    • h: number
    • s: number
    • l: number
    • a: number

instanceProperty

  • h: number
  • s: number
  • l: number
  • a: number

{
  lineno: 3,
  column: 12,
  filename: 'index.css',
  h: 11,
  s: 22,
  l: 33,
  a: 0.4,
  hsla: [Circular]
}

Ident

autoinheritnone 等のトークンを表現するクラス。

constructor

  • Ident(name, val, mixin)
    • name: string

instanceProperty

  • name: string
  • string: string

{
  lineno: 1,
  column: 13,
  filename: 'index.css',
  name: 'auto',
  string: 'auto',
  val: null,
  mixin: false
}

Call

cubic-bezier() 等の関数呼び出しを表現するクラス。

constructor

  • Call(name, args)
    • name: string
    • args: expression

instanceProperty

  • name: string
  • args: expression

{
  lineno: 1,
  column: 40,
  filename: 'index.css',
  name: 'cubic-bezier',
  args: {
    lineno: 1,
    column: 20,
    filename: 'index.css',
    nodes: [ [Object], [Object], [Object], [Object] ],
    isList: undefined,
    map: {}
  }
}

テスト

記事の公開時点では Stylus v0.54.5 を対象にテストしています。
all-user/inspect-stylus

Stylus の変数に JS からアクセスする方法を考える

transitionanimationが使えるようになり、擬似セレクタの種類も増えてきたことで、スタイル周りの多くのことがCSSだけ(あるいはclassListaddremove程度の操作)で書けるようになってきた気がする。

それでも、どうしても JavaScript で操作したい時もまだあって、 Stylus で利用している変数を JavaScript 内でも参照したくなることがある。

いくつか方法を試した結果、結局 Stylus のプラグインを自作することにした。

www.npmjs.com

Atom + pigments

やりたかったことをまとめると、

  • Stylus の変数定義ファイルが存在すること
  • JS からは "20px" のような文字列ではなく、可能な限り 20 のように使用しやすい形で取得できること
  • 実際に変数の定義を書く場所は JS でも Stylus でも良い
  • JS 側、 Stylus 側共にコーディングの制約が少ないこと

という感じです。

Stylus の変数定義ファイルが欲しい理由は、

この pigments というプラグインが Stylus の構文をある程度解釈してくれるので、変数にもハイライトが効くようになる。これが大変便利。 (ここでいう変数定義ファイルとはdefine(key, value)ではなく、key = valueの構文で代入しているコード)

f:id:alluser:20160925214505j:plain

検討した他の手段

調べてみると既にいろんな方法で同じような試みがあった。

rosetta

Stylusだけでなく、Sass、LESSで使うことも出来る。 JS => Stylus でも Stylus => JSでもなく、.rose という拡張子の rosetta ファイルから JS と Stylus(もしくは Sass、LESS)を書き出す仕組みになっている。 最初 README を読んだ時、Stylus で変数定義して rosetta を require すればあら不思議、Stylus の変数が使えちゃう、みたいなノリで書いてあるから勘違いしたがどうやら違う。

それでも rosetta ファイルが Stylus と同じ様に書けるならまあ問題ないかと思い、実際に rosetta ファイルを作ってコンパイルしてみたが、コンパイルエラーになった。 rosetta ファイルは Stylus とシンタックスは同じと書いてあるが、変数名の-や、タプルの代入などはシンタックスエラーになる。
ということは、つまり Stylus の ファイルをそのまま使えないということになるし、define メソッドなどで定義されたものも共有出来ない。
既存の Stylus が流用できないのは面倒くさそうなので使うのはやめた。

stylus-export-loader

webpack 用ではあるがやりたいことには概ねマッチしている感じがする。 なぜか npm に登録されてないので github リポジトリを package.json に書いてインストールする。

変数定義用の Stylus ファイルを用意しておき、 !stylus-export-loader! + そのパスを webpack.ProvidePlugin 経由で読み込むことで任意のネームスペースから Stylus の変数にアクセスできるようになる(と思ったけど結局うまく動かなかったよ。)

stylus-vars, stylus-var

どちらも Stylus の変数を JS で定義出来るよーというもの。 Stylus の変数定義ファイルが生成されないのでこれも見送った。

そこで foovar ですよ

foovar を使うと Stylus の変数を JS ファイルに書き出す事ができる。
Stylus の実行時に変数スコープを調べるので、Stylus 側では制約無く変数を定義して使うことが出来る。
変数定義ファイルも用意出来る。
これでやりたかったことは全部できるようになった。

インストール

$ npm i -D foovar

使い方

CLI の場合

$ stylus -u foovar path/to/file.styl

webpack + stylus-loader の場合

// webpack.config.js
module.exports = {
  stylus: {
    use: [require('foovar')()]
  }
};

Stylus ファイルの任意の場所で foovar(path) を呼び出す。
その時点で定義されている変数が JS ファイルにエクスポートされる。

foo = 10px

foovar('src/StyleDefinitions.js')

エクスポートされた JS は require して使うことが出来る。

const StyleDefinitions = require('./src/StyleDefinitions.js');

StyleDefinitions.foo(); // 10
StyleDefinitions.foo.type; // 'px'
StyleDefinitions.foo.css; // '10px'

エクスポートできる変数のタイプ

stringunitidentrgbahslacubic-beziertuplelisthash を書き出すことが出来る。
unitpx%emmsmm …等の単位付き(あるいは無し)の数値のことで、 varName.type はその単位を返す(無しの場合は undefined)。

Stylus:$var-name JS:varName() JS:varName.type JS:varName.css
'some text' 'some text' 'string' 'some text'
20px 20 'px' '20px'
50% 50 '%' '50%'
255 255 undefined '255'
auto 'auto' 'ident' 'auto'
#112233 [17,34,51,1] 'rgba' '#112233'
#11223344 [17,34,51,0.26666666666666666] 'rgba' '#11223344'
rgba(11,22,33,.4) [11,22,33,0.4] 'rgba' 'rgba(11,22,33,0.4)'
hsl(11,22%,33%) [11,22,33,1] 'hsla' 'hsla(11,22%,33%,1)'
hsla(11,22%,33%,.4) [11,22,33,0.4] 'hsla' 'hsla(11,22%,33%,0.4)'
cubic-bezier(1,0,1,0) [1,0,1,0] 'cubic-bezier' 'cubic-bezier(1,0,1,0)'
10px 20px 30px 40px [FoovarValue instance x 4] 'tuple' undefined
1em, 2em, 3em, 4em [FoovarValue instance x 4] 'list' undefined
{ foo: 1em } { foo: FoovarValue instance } 'hash' undefined

tuplelisthash に関しては、配列・オブジェクトの各要素がそれぞれ typecss を持つ FoovarValueインスタンスになっている。
各要素は単項の値と同じように typecss へアクセス出来る。

// foo = 10px 20px 30px 40px
// bar = { baz: 1em }
const StyleDefinitions = require('./src/StyleDefinitions.js');

StyleDefinitions.foo()[0]() // 10
StyleDefinitions.foo()[1].type // 'px'
StyleDefinitions.foo()[2].css // '30px'

StyleDefinitions.bar().baz() // 1
StyleDefinitions.bar().baz.type // 'em'
StyleDefinitions.bar().baz.css // '1em'

普段よく自分がアクセスしたくなりやすいものを中心に実装した。URL とかはまだ対応していない。
いずれ欲しくなったときに追加するかも知れないししないかもしれない。

Stylus の変数にアクセスしたくなった時は foovar をぜひに。