いまどきのJavaScriptライブラリ開発

最近、仕事上でJavaScriptライブラリを作って公開することが多くなってきました。その際のノウハウが溜まってきたので先日公開したSmartPhoto.jsを例にとって紹介したいと思います。
SmartPhoto.jsでは以下の5つの方法で開発したことが、いまどきっぽいなと感じています。

  • Babel Browserifyを使ったモダンコーディング
  • eslintを使ってソースコードの品質を担保
  • jQuery未使用
  • git tagとnpmとのバージョニングの紐付け
  • CircleCIを使ったテスト

1. Babel Browserifyを使ったモダンコーディング

browserify ./src/index.js -t babelify -p licensify --standalone smartPhoto -o ./js/smartphoto.js

上記のように、browserify babelifyを使って、es2015でJavaScriptをかける環境にしています。一番便利なのが、モジュールのimportやexportが使えることですね。classをサポートしているのもいいです。また自分はhtmlもimportして使いたいためbabelrcにそのためのプラグインも追加しています。

babelrc

{
  "presets": [
    "es2015"
  ],
  "plugins": ["transform-html-import-to-string"]
}

BrowserifyやWebpackをお使いの人はnpm経由でこのJavaScriptライブラリが使えるように、SmartPhotoクラスのexportもしています。

module.exports = SmartPhoto

2. eslintを使ってソースコードの品質を担保

またeslintを使ってソースコードの品質を担保しています。またプリセットとして、eslint-config-airbnbを使っているのですが、ルールが厳しすぎるので以下のように一部のルールを殺しています。

{
  "extends": [
    "airbnb/base"
  ],
  "env": {
    "browser": true
  },
  "globals": {
    "document": true,
    "window": true,
    "event": true
  },
  "rules":{
    "comma-dangle": 0,
    "no-underscore-dangle": 0,
    "class-methods-use-this": 0,
    "no-param-reassign": 0,
    "no-mixed-operators": 0,
    "max-len": 0,
    "no-continue": 0,
    "no-undef": ["error", { "typeof": true }],
    "no-restricted-properties": 0
  }
}

3. jQuery未使用

SmartPhoto.jsはjQueryに対応した、jquery-smartphoto.jsというscriptも同時に配布しているのですが、実を言うと内部的には全くjQueryを使用していません。理由としては2つあって、一つはBrowserifyを使ってbundleした際のファイルサイズを抑えるためです。もしjQueryを利用する場合はjQueryもimportする必要があります。最近ではjQueryを使わないプロダクトの方がwebpackやbrowserifyなどのbundleツールとの相性がいいため重宝されるのも理由の一つです。
生のJavaScriptを書くのは以外と辛いです。普段どれだけjQueryに助けられていたかを改めて実感しました。。。ちなみに以下のサイトを参考にして実装しました。


以下のように、よく使うjQueryの便利な機能は以下のように小さなモジュールに分解してexportしておくと便利です。

module.exports.append = (element,string) => {
  const parser = new DOMParser();
  const doc = parser.parseFromString(string, 'text/html');
  element.appendChild(doc.querySelector('body').childNodes[0]);
}

module.exports.addClass = (element,className) => {
  if (element.classList) {
    element.classList.add(className);
  } else {
    element.className += ` ${className}`;
  }
}

module.exports.removeClass = (element,className) => {
  if (element.classList) {
    element.classList.remove(className);
  } else {
    element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
  }
}

なお、jQuery版のjquery-smartphoto.jsはインターフェースとして、$.fn.smartPhotoを呼び出せるようにしていますが、ただ内部的にはnewしているだけです。

/adaptor/jquery.js

const applyJQuery = (jQuery) => {
  jQuery.fn.smartPhoto = function(settings) {
    if (typeof settings === 'strings'){
    } else {
      new smartPhoto(this.selector,settings);
    }
    return this;
  }
}

4. git tagとnpmとのバージョニングの紐付け

npm install smartphoto --save

また、SmartPhotoはnpmに公開しているため上記のコマンドでダウンロードしてimportして使うことができます。npmに新しいバージョンのパッケージを公開する際に同時にgitにもtagがつくようにしています。


Releases

npm


どのようにバージョン管理をしているかというと、まず公開前にテストのスクリプトを動かし、その後npm version patchというコマンドでpackage.jsonのバージョンを書き換え、jsをビルドした後、gitなどにタグ付けやpushを行うscriptを実行しています。以下がその部分のnpm scriptsです。


package.jsonの一部

"patch": "npm run test && npm version patch && npm run build:js && node ./tools/index.js",
"minor": "npm run test && npm version minor && npm run build:js && node ./tools/index.js",
"major": "npm run test && npm version major && npm run build:js && node ./tools/index.js"

tools/index.js

"use strict"
const cmd = require('node-cmd');
const co = require('co');
const fs = require('fs-extra');
const pkg = require('../package.json');

const SystemPromise = (cmd_string) => {
  return new Promise((resolve, reject) => {
    cmd.get(
      cmd_string,
      (data, err, stderr) => {
        if ( err ) {
          console.log(err)
        }
        if ( stderr ) {
          console.log(stderr)
        }
        resolve(data)
      }
    )
  })
}

co(function *() {
  try {
    yield SystemPromise(`git add -A`);
    yield SystemPromise(`git commit -m "v${pkg.version}"`);
    yield SystemPromise(`git push origin v${pkg.version}`);
    yield SystemPromise(`git push origin master`);
    yield SystemPromise(`npm publish`);
  } catch ( err ) {
    console.log(err)
  }
});

CircleCIを使ったテスト

また、テストを手動で毎回テストするのは非常に大変です。
CircleCIを使えばgithubにpushした段階で勝手にテストコードを実行してくれるので非常に便利です。circle.ymlではnode.jsのバージョンを指定して、テスト時のコマンドなどを定義することができます。

circle.yml

machine:
  node:
    version: 6.2.0
dependencies:
  override:
    - "npm install"
test:
  override:
    - "npm run test"

テストには現在のところmocha, power-assert, nightmare.jsを使っています。そのあたりのことは以前にブログを書きました。


まとめ

最近だいぶ、いまどきのライブラリの開発手法が確立されてきたんじゃないかなと思います。次はFlowを使った型定義とかでしょうか?
何か気づいた点がありましたら優しく教えてくだされば幸いです。

SmartPhoto.jsのページ



堀 悟大

アップルップル フロントエンドエンジニア。2014年高知大学理学部卒業。学生時代にHTML5のCanvas要素を使ってゲームを作っていたことでWeb全般に興味をもつ。アップルップル入社後はa-blog cmsを便利に使うための機能の実装や、HTML5の技術を使ったデジタルサイネージの実装を行う。趣味は英語。読むことも話すことも好き。

Home
Next entry →