stylusのmixinをプラグインとして公開する

動機
---
* [nib](https://github.com/tj/nib#readme)
* [kouto-swiss](https://github.com/leny/kouto-swiss#readme)
* [autoprefixer-stylus](https://github.com/jescalan/autoprefixer-stylus#readme)
* [typographic](https://github.com/corysimmons/typographic#readme)
* [jeet](https://github.com/mojotech/jeet/tree/master/stylus#readme)
* [rupture](https://github.com/jescalan/rupture#readme)

だいたいの問題は上記が解決してくれるとして、それ以外のよく使う方言をstylusプラグインとして公開したい場合があると思うので、自分が実践した手順を紹介します。

環境
---
`node-v6.2.1` および 下記`package.json`を使用します。

```bash:bash
mkdir foo
cd foo
vim package.json
npm install
```

```json:package.json
{
  "scripts": {
    "build": "stylus index.styl --out index.css",
    "test": "ava",
    "lint": "xo"
  },
  "devDependencies": {
    "ava": "^0.15.2",
    "stylus": "^0.54.5",
    "xo": "^0.15.1"
  },
  "xo": {
    "space": 2
  }
}
```

`stylus`に認識させる
---
stylusは`@import`を使用したさいに、登録してある`paths`から順にファイルを精査します。

CLIから`--include`オプションを使用せずコンパイルした場合、現在のディレクトリを基準にするため、以下は正常に動作します。

```bash:bash
tree -L 1
# .
# ├── index.styl
# └── my-plugin.styl

npm run build
# h1 {
#   font-size: 10vw;
# }
```
```stylus:./index.styl
@import 'my-plugin'
```
```stylus:./my-plugin.styl
h1
  font-size 10vw
```

JavaScriptから実行した場合も同様です。

```js:build.js
const fs = require('fs');
const stylus = require('stylus');

const entry = fs.readFileSync('index.styl', 'utf8');
stylus(entry)
.render((err, css) => {
  console.log(css);
  // h1 {
  //   font-size: 10vw;
  // }
});

```

`node_modules`として動作させる
---

既存のプラグインは`npm install`で`node_modules`にインストールできますが、使用する前に`--use my-plugin`なり、`.use(myPlugin())`などで登録を行います。

いずれにしても、プラグインを`require`し、`require`したディレクトリ直下の`index.js`か、同じくディレクトリ直下の`package.json>main`に定義されている位置のファイルを読み込み、`module.exports`で定義されている関数が実行されるはずです。

```js:./index.js（最低限必要なプラグインの初期化処理）
module.exports = function () {
  return function (stylus) {
    stylus.include(__dirname);
  };
};
```

[nibの実装](https://github.com/tj/nib/blob/master/lib/nib.js#L51)では、実行した関数が関数を返し、その中で初期化処理を行っています。
注目すべきは最初の`style.include(__dirname)`という箇所で、これはstylusの`paths`にそのディレクトリ(`node_modules/nib/lib`)を登録しています。ですので、この状態で前章の`index.styl`に`@import 'nib'`と書くと、`node_modules/nib/lib`の`nib/index.styl`または`nib.styl`が読み込まれます。

> 仮に、先に登録した`paths`に同名のフォルダやファイルが存在していた場合、そちらが優先されるようです。

この動作は、実際に`node_modules`内にフォルダを作成してみて、CLIの動作を確認してみるとイメージしやすいです。

```
mkdir node_modules/my-plugin2
vim node_modules/my-plugin2/index.js
vim node_modules/my-plugin2/my-plugin2.styl
```

```js:node_modules/my-plugin2/index.js
module.exports = function () {
  return function (stylus) {
    stylus.include(__dirname);
  };
};
```
```styl:node_modules/my-plugin2/my-plugin2.styl
h2
  font-size 5vw
```

CLIで`--use　my-plugin2`または`--include node_modules/my-plugin2/`で、`my-plugin2`を`paths`に追加すると、コンパイルに成功することを確認できます。

```stylus:./index.styl
@import 'my-plugin'
@import 'my-plugin2'
```
```bash:bash
npm run build
# failed to locate @import file my-plugin2.styl

npm run build -- --use my-plugin2
npm run build -- --include node_modules/my-plugin2/
# h1 {
#   font-size: 10vw;
# }
# h2 {
#   font-size: 5vw;
# }
```

テスト環境の整備
---
描写結果を確認したい場合は [karma](http://qiita.com/tags/karma) を使い、ブラウザが計算したスタイルを検証するべきでしょうが、この記事では言及しません。
今回は、コンパイルした`styl`が指定の`css`に一致するかのテストを[ava](https://github.com/sindresorhus/ava-docs/blob/master/ja_JP/readme.md) + [xo](https://github.com/sindresorhus/xo#readme)で行います。

> `xo@0.15`は[babel-eslint@6](https://github.com/babel/babel-eslint#readme)を含んだ`linter`です。生[eslint](https://github.com/eslint/eslint#readme)に比べて、プロジェクト自体の`devDeps`を減らせるので、プロジェクトを小さくしたい場合にお勧めです。

これまでの章で作成した`index.js`, `my-plugin.styl`以外のファイルは以降使用しないので、削除してください。

```bash:bash
tree -L 1
# .
# ├── index.js
# ├── my-plugin.styl
# ├── node_modules
# ├── package.json
# └── test.js
```

`./my-plugin.styl`を`mixin`に変更します。

```stylus:./my-plugin.styl
large-font()
  font-size 10vw
```

`npm test`で使用するテスト本体を記述します。

```js:./test.js
import test from 'ava';
import stylus from 'stylus';

import myPlugin from './';

const render = (str) => {
  let css;
  stylus(str)
  .use(myPlugin())
  .import('my-plugin')
  .set('compress', true)
  .render((err, data) => {
    if (err) {
      throw err;
    }

    css = data;
  });
  return css;
};

const specs = [
  {
    description: 'h1のfont-sizeを10vwにすべき',
    code: `
    h1
      large-font()
    `,
    expected: 'h1{font-size:10vw}'
  }
];

specs.forEach(spec => {
  test(spec.description, t => {
    const {code, expected} = spec;
    t.true(render(code) === expected);
  });
});
```

`compress`オプションを利用することでテストしやすい`css`が生成できるので、stylusの初期化処理を含めてヘルパ関数として定義します。`test`内ではヘルパの実行と、結果の比較のみを行い、テストデータ`specs`を増やしていくことで、検証内容を自動で増やすことが可能です。

`npm test`でテストが通ること、テスト内容を変更して失敗することを確認します。

```bash:bash
npm test
# 1 passed

vim test.js

npm test
# 1 failed
```

実施例
---

> [59naga/stylus-responsive-breakpoints: a media queries of stylus block mixins](https://github.com/59naga/stylus-responsive-breakpoints#readme)

[angular-materialのメディアクエリのテンプレート](https://material.angularjs.org/latest/layout/container)を`stylプラグイン`として移植したものです。他に`iphone`, `ipad`を`device-width`で検出するメディアクエリなどを追加しています。