動機
だいたいの問題は上記が解決してくれるとして、それ以外のよく使う方言をstylusプラグインとして公開したい場合があると思うので、自分が実践した手順を紹介します。
環境
node-v6.2.1 および 下記package.jsonを使用します。
mkdir foo
cd foo
vim package.json
npm install
{
"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オプションを使用せずコンパイルした場合、現在のディレクトリを基準にするため、以下は正常に動作します。
tree -L 1
# .
# ├── index.styl
# └── my-plugin.styl
npm run build
# h1 {
# font-size: 10vw;
# }
@import 'my-plugin'
h1
font-size 10vw
JavaScriptから実行した場合も同様です。
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で定義されている関数が実行されるはずです。
module.exports = function () {
return function (stylus) {
stylus.include(__dirname);
};
};
nibの実装では、実行した関数が関数を返し、その中で初期化処理を行っています。
注目すべきは最初の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
module.exports = function () {
return function (stylus) {
stylus.include(__dirname);
};
};
h2
font-size 5vw
CLIで--use my-plugin2または--include node_modules/my-plugin2/で、my-plugin2をpathsに追加すると、コンパイルに成功することを確認できます。
@import 'my-plugin'
@import 'my-plugin2'
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 を使い、ブラウザが計算したスタイルを検証するべきでしょうが、この記事では言及しません。
今回は、コンパイルしたstylが指定のcssに一致するかのテストをava + xoで行います。
xo@0.15はbabel-eslint@6を含んだlinterです。生eslintに比べて、プロジェクト自体のdevDepsを減らせるので、プロジェクトを小さくしたい場合にお勧めです。
これまでの章で作成したindex.js, my-plugin.styl以外のファイルは以降使用しないので、削除してください。
tree -L 1
# .
# ├── index.js
# ├── my-plugin.styl
# ├── node_modules
# ├── package.json
# └── test.js
./my-plugin.stylをmixinに変更します。
large-font()
font-size 10vw
npm testで使用するテスト本体を記述します。
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でテストが通ること、テスト内容を変更して失敗することを確認します。
npm test
# 1 passed
vim test.js
npm test
# 1 failed
実施例
59naga/stylus-responsive-breakpoints: a media queries of stylus block mixins
angular-materialのメディアクエリのテンプレートをstylプラグインとして移植したものです。他にiphone, ipadをdevice-widthで検出するメディアクエリなどを追加しています。