npmでもファイル直接でも利用できるブラウザ用JavaScriptライブラリ開発環境構築


npmからインストールしてbroserify/webpack経由だけでなく、ファイルを直接ダウンロードしても使って欲しいブラウザ用ライブラリを作る環境を整える。

## 前提条件
* babelにてES2015(ES6)を利用する
* npm経由でインストールできる
* ファイルを直接ダウンロードしても利用できる
* githubに公開する
* テストはkarma
* TravisCIを利用する
* saucelabsでマルチブラウザのテストをする



以下で時々「saiji」という文字がでてくるが、これは自作のライブラリの名前である。適宜ご自分のライブラリ名に読み替えていただきたい。

## ディレクトリ構成概要

![](http://img.f.hatena.ne.jp/images/fotolife/n/niwaringo/20160314/20160314220346.png)

ES2015のコードを`src`ディレクトリに書いてbabelで`lib`に出力。`lib`に出力されたものをtop直下にbrowserifyで出力する。そのファイルをダウンロードすればnpmなしでも利用できるようになる。

テストはbrowserifyされたファイルのみが対象。

## git

![](http://img.f.hatena.ne.jp/images/fotolife/n/niwaringo/20160314/20160314222859.png)

gitでは`lib`、`node_module`は管理対象から外す。

```.gitignore
/lib
node_module
```

## npm

![](http://img.f.hatena.ne.jp/images/fotolife/n/niwaringo/20160314/20160314223258.png)

pachake.jsonの`files`を指定して、npmにはbabelでトランスパイルされた`lib`のみ公開するようにする。
mainはlib以下を指定。

```package.json
"main": "lib/saiji.js",
"files": [
  "lib"
]
```




## babel
ES2015のために`babel-cli`、　`babel-preset-es2015`をインストールして、`.babelrc`ファイル作成。

```bash
npm i -D babel-cli babel-preset-es2015
echo '{ "presets": ["es2015"] }' > .babelrc
```

npmのscriptsに`babel`でsrc以下をlibに出力。、`watch:babel`でwatchする。

```package.json
"scripts": {
  "babel": "babel src -d lib",
  "watch:babel": "babel src -d lib -w"
}
```

## browserify
browserifyを利用して、ファイル直接読み込みでも利用できるようにする。

`browserify`、差分ビルドのために`watchify`をインストール。

```bash
npm i -D browserify watchify
```

`build`でlib/saiji.js(endpoint)をTOPディレクトリ以下に出力する。

`build`でbrowserify、`watch:build`でwatchifyを利用する。`-s`オプションをつけてグローバルを生やしておく。

```package.json
"scripts": {
  "build": "browserify lib/saiji.js -o saiji.js -s saiji",
  "watch:build": "watchify lib/saiji.js -o saiji.js -s saiji -v",
}
```

## babel&browserify

実際に開発しているときは、babel => browserify をwatchしておきたい。そのための`watch`をpackage.jsonのscriptsに登録しておく。

```package.json
"scripts": {
  "babel": "babel src -d lib",
  "build": "browserify src/saiji.js -o saiji.js -s saiji",
  "watch:babel": "babel src -d lib -w",
  "watch:build": "watchify lib/saiji.js -o saiji.js -s saiji -v",
  "watch": "npm run watch:babel & npm run watch:build"
}
```

## karma
`window`や`document`を含むDOMをカジュアルにつかえるため、テストはkarmaを利用する。
フレームワークは`mocha`、assertは`chai.assert`を使う。

karmaをインストールしたら`karma init`で`karma.conf.js`を作成する。

```bash
npm i -D karma mocha chai karma-chai
./node_modules/.bin/karma init
```

karma.conf.jsのframeworksにchaiを追加する。

karmaのpreprocessorsでbabel,browserifyすると、パフォーマンス的にストレスがたまる‥‥　そのため、babel,browserify済みのTOPディレクトリのファイルをテスト対象にしてpreprocessorsは使わない。 (本当はpowerassert使いたいんだけど :cry: )

```karma.conf.js
frameworks: ['mocha', 'chai'],
files: [
    'saiji.js',
    'test/*.js'
],
preprocessors: {}
```

`npm test`でkarmaが起動するようにしておく。

```package.json
"scripts": {
  "test": "karma start",
  ...
},
```


## TravisCI
TravisCIを使いたい、というかバッジが欲しい :smirk:

LTSの4系と最新の5系を使い、karmaでChrome(chromium)を使う設定を`before_install`に書く。

```.travis.yml
language: node_js
node_js:
  - "4"
  - "5"

before_install:
  - export CHROME_BIN=chromium-browser
  - export DISPLAY=:99.0
  - sh -e /etc/init.d/xvfb start
```

TravisCIでkarmaを使う場合は、singleRunをtrueにしなければならなかったりするので、karma.conf.jsを変更。

```karma.conf.js
var customLaunchers = {};
var browsers = ["Chrome"];
var singleRun = false;

if (process.env.TRAVIS) {
    customLaunchers = {
        Chrome_travis_ci: {
            base: 'Chrome',
            flags: ['--no-sandbox']
        }
    };
    browsers = ["Chrome_travis_ci"];
    singleRun = true;
}
module.exports = function(config) {
    config.set({
    	//関係ない部分は省略
       browsers: browsers,
       singleRun: singleRun,
    })
};
```

## SauceLabs
マルチブラウザのテストのためにSauceLabsを使う。
karmaからSauceLabsを使うには、`karma-sauce-launcher`を使うので、まずはインストール。

```bash
npm i -D karma-sauce-launcher
```

karma側にテストするブラウザ情報を渡してやることになるのだけれど、karma.conf.jsにずらずら書くと可読性が落ちるので、ブラウザ管理部分を別ファイルにする。

`customlaunchers.js`というファイルをつくり、そこにテストするブラウザを羅列。

```customlaunchers.js
var version = require("./package.json").version;
// Check out https://saucelabs.com/platforms
module.exports = {
  "sl_win10_chrome": {
    "base": 'SauceLabs',
    "browserName": "chrome",
    "platform": "Windows 10",
    "version": "48",
    "build": version,
    "passed": true
  },

  "sl_win10_firefox": {
    "base": 'SauceLabs',
    "browserName": "firefox",
    "platform": "Windows 10",
    "version": "44",
    "build": version,
    "passed": true
  },

/// こんな感じで羅列

};

```

`npm run sauce`とすればSauceLabsのテストをできるようにする。

``` package.json
"scripts": {
  "sauce": "Sauce=true karma start"
}
```

karma.conf.js側で環境変数をみて作成したcustomlaunchers.jsを読み込む。
OpenSource版のSauceLabsは同時接続が5接続までなので、concurrencyの設定をする。


``` karma.conf.js
var customLaunchers = {};
var browsers = ["Chrome"];
var reporters = ['progress'];
var singleRun = false;
var concurrency = Infinity;

if(process.env.Sauce) {
    customLaunchers = require("./customlaunchers");
    browsers = Object.keys(customLaunchers);
    reporters = ['dots', 'saucelabs'];
    singleRun = true;
    concurrency = 5;
}

module.exports = function(config) {
    config.set({
       	//関係ない部分は省略
        sauceLabs: {
            testName: "test name",
            username: "SauceLabs username",
            accessKey: "SauceLabs accessKey",
            public: "public"
        },
        customLaunchers: customLaunchers,
        browsers: browsers,
        reporters: reporters,
        singleRun: singleRun,
        concurrency: concurrency
    })
};
```

## karma.conf.js

karma.conf.jsの最終形はこんな感じ

```karma.conf.js
var customLaunchers = {};
var browsers = ["Chrome"];
var reporters = ['progress'];
var singleRun = false;
var concurrency = Infinity;


if (process.env.TRAVIS) {
    customLaunchers = {
        Chrome_travis_ci: {
            base: 'Chrome',
            flags: ['--no-sandbox']
        }
    };
    browsers = ["Chrome_travis_ci"];
    singleRun = true;
} else if(process.env.Sauce) {
    customLaunchers = require("./customlaunchers");
    browsers = Object.keys(customLaunchers);
    reporters = ['dots', 'saucelabs'];
    singleRun = true;
    concurrency = 5;
}

module.exports = function(config) {
    config.set({
        basePath: '',
        frameworks: ['mocha', 'chai'],
        files: [
            'saiji.js',
            'test/*.js'
        ],
        exclude: [],
        preprocessors: {
        },
        port: 9876,
        colors: true,
        logLevel: config.LOG_INFO,
        autoWatch: true,
        sauceLabs: {
            testName: "test name",
            username: "sauce username",
            accessKey: "sauce accessKey",
            public: "public"
        },
        customLaunchers: customLaunchers,
        browsers: browsers,
        reporters: reporters,
        singleRun: singleRun,
        concurrency: concurrency
    })
};
```

***

というわけで、npmでもファイル直接でも利用できるブラウザ用JavaScriptライブラリをつくる環境ができあがったというわけです‥‥　


が、これ、大変すぎやしないですかね。

牧歌的な`;(function(){}())`で囲まれたJavaScriptをQUnitでテストしていたころが懐かしい :cry:


## 参考リンク

- [ライブラリをES6で書いて公開する所から始めよう](http://efcl.info/2015/01/09/write-es6/)