ライブラリをES2015(ES6)で書いて公開する所から始めよう
この記事はECMAScript 2015の事始めとして、ライブラリをECMAScript 2015で書いて公開するというところから始めるのがいいのではという内容です。 ECMAScript 2015(ES2015)はES6とも呼ばれていてどちらも同じものを指しますが、この記事ではES2015に統一します。
ECMAScriptのバージョンについては次のページを参照してください。
2018-12-27: 追記
- textlint/textlint-rule-helperのmasterはTypeScriptの実装へ変換されています。
- Babelの実装はhttps://github.com/textlint/textlint-rule-helper/tree/2.0.1から参照できます
- Babel から TypeScript への変換方法については次の記事で紹介しています
- Babelで書かれたライブラリをTypeScriptへ移行する方法 | Web Scratch
2018-10-06: 追記
- ES6 を ES2015と書くように統一しました。
- Babel 7を使うように書き直しました
- テスト周りの記述を簡略化しました
- ドキュメントについての記述を簡略化しました
- Update to Babel 7 by azu · Pull Request #162 · efcl/efcl.github.io
2016-08-31: 追記
- Babelとpower-assertの設定方法を.babelrcを使うように変更しました
- Babel 6から.babelrcが必須になりました
- espower-babelは非推奨になりました
- power-assert + babel as a development tool | Web Scratch
2015-05-09: 追記
- 6to5 は Babel へリネームされました。
- azu/espower-babelはテストコード以外もBabelで変換するようになりました
- テストから
import hoge from "../lib/hoge"
ではなくimport hoge from "../src/hoge"
を参照できるようになりました
- テストから
ES2015 -> ES5
ES2015は策定が完了し、メジャーなブラウザの最新版は既に実装されています。
しかし、現実にはIE 11など最新版以外の実行環境でも動くように書くと思います。 また、ECMAScriptは毎年仕様がアップデートされるため、最新の仕様がすべてのブラウザで実装されているとは限りません。 そのため、新しい構文を含むコードをここではES5相当のコードに変換して利用します。
ES5の構文ならばIEを含むほぼすべてのブラウザで動作します。
今すぐ、ES2015でコードを書いてES5に変換するツールは色々あります。
少し注意したほうがいいのは、上記のツールは基本的には構文(class
やmodule
など)の変換だけをサポートする点です。
構文とはサポートしてないコードをは読み込んだだけでシンタックスエラーとなるものを考えると分かりやすいです。
一方、Array.from
やPromise
といったAPIは別のpolyfillと呼ばれるライブラリを使います。
polyfillとは仕様と同じ機能を実現するライブラリのことです。
そのため、変換ツールで変換したからといってすべてのES2015のAPIが古いブラウザで動作するわけではないことには注意が必要です。
たとえばTypeScriptはデフォルトでは構文のみを変換するため、TypeScriptで変換したからといってIE11でArray.from
が使えるわけではありません。
またES5へ変換やPolyfillを入れてもProxy
などES5の機能ではエミュレートできないものも存在することには注意が必要です
polyfillについては次のものが有名です。
簡単にまとめると、ES2015以降の構文を使うことでコードもシンプルになりますが、それを公開するためには変換ツールで変換して使うのが現状だと思います。
ライブラリをES2015で書いてnpmで公開してみよう
じゃあ手始めにどういう時にES2015で書くのがいいかというと、ライブラリとして公開するようなコードをES2015で書くのがいいと思います。
具体的な例と共にES2015で書いてnpmでライブラリを公開するまでを見て行きたいと思います。
扱う題材をシンプルにするためにNode.jsのライブラリとして考えます。
以下のtextlint/textlint-rule-helperというものを例にして見ていきます。(このライブラリはtextlintというツールのルールを書くのを補助するライブラリですが今回はどうでもいいです)
上記のリポジトリをみてもらうと、ES5のコードやgulp等の設定ファイルも存在しない事がわかると思います。
]
追記(2018-12-27): textlint/textlint-rule-helperのmasterはTypeScriptの実装へ変換されています。 そのため、Babelを使った実装を参照したい方は 2.0.1 のバージョンのリポジトリを参照してください。
ローカルでBabelの実装をcheckoutしたい場合は次のようにタグをcheckoutしてください。
git clone [email protected]:textlint/textlint-rule-helper.git
cd textlint-rule-helper
git checkout 2.0.1
ライブラリの構成
ライブラリの構成は以下のようになっています。
src/
: ES2015で書かれたコードlib/
: babelでsrc/
以下のコードを変換したES5のコードlib/
ディレクトリは.gitignore
で無視して、リポジトリには含めない- 変換後のソースはGitで管理しない(そうした方がPull Request時に迷わない)
- 逆に
package.json
のfiles
フィールドでは"files" : ["lib"]
のみとする - npmが軽量になるし、npmで取ってきたファイルは
lib/
以下のES5のコードのみにできる - 細かすぎて伝わらない package.json 小ネタ三選 - t-wadaのブログ
test/
: ES2015 + mochaでテストを書くREADME.md
: ドキュメント- ライブラリにはドキュメントは必要。いくら良いコードでもドキュメントがないとダメ
- JSDocでドキュメントを書いてjsdoc-to-markdownでMarkdownに変換して、READMEに貼り付ける
ざっくり見るとtextlint/textlint-rule-helperは上記のような構成で動いています。
(OmniGraffleのアイコンにディレクトリをD&Dするとこういう図ができる - OmniGraffle Folder Trick — MacSparky)
src/
ES2015で書いたコードを置く場所です。
src/
はソースコードを置く場所として一般的に使われているディレクトリです。
lib/
実際に配布されるJavaScriptのコードを置く場所です。
Node.jsなど変換しないでコードを書く場合には、直接lib/
以下にJavaScriptのソースをおいてると思います。
ここに置かれるJavaScriptファイルはsrc/
のES2015で書かれたものをES5に変換したものが配置されます。
lib/
はsrc/
から生成されるため、Gitで管理する必要性はありません。
BabelでES2015からES5の変換
ES2015 -> ES5の変換にはBabelを使います。
devDependencies
で入れておいて、npm run-scriptで実行するようにすればglobalに入れる必要はありません。
(Node.jsのプロジェクトにおいては、グローバルではなくローカルに依存モジュールを入れるのが一般的です。)
次のようにBabelとES2015からES5への変換するルールを含むpreset-envをdevDependencies
へインストールします。
- Usage Guide · Babel
- Babelの公式ドキュメントも参照してください。
npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/register
そして、変換にどのpresetを使うかを.babelrc
というファイル名で作成して次のような内容にします。
ここでは、@babel/preset-envだけを使うという指定になります。
@babel/preset-envではオプションで変換対象を細かく制御できますが、デフォルトではES5相当のコードへ変換してくれます。
.babelrc
:
{
"presets": [
"@babel/preset-env"
]
}
npm run-scriptsの追加
次のようにpackage.json
のscripts
フィールドにbuild
、watch
、test
のスクリプトを追加します。
...
"scripts": {
"build": "babel src/ --out-dir lib/ --source-maps",
"watch": "babel src/ --out-dir lib/ --watch --source-maps",
"test": "mocha test/ --require @babel/register",
"prepublish": "npm run build"
}
...
このnpm run-scriptsは次のように、npm run <コマンド名>
で実行できます。
例) babelを使ってsrc/
のコードを変換し、lib/
に出力する
npm run build
prepublish
はライフサイクルコマンドで、npm publish
を行う際に自動的に実行されます。
この場合はnpm publish
する際に自動的にビルドを行うことで、ビルド忘れを防止できます。
.gitignore
で変換後のファイルを管理下から外す
しかし、変換したJavaScriptにPull Requestとか送られても困ります。
そのためGitリポジトリにはlib/
は含めないようにします。
.gitignore
に以下のように書いて、lib
ディレクトリを無視させます。
/lib
このように設定すればGitHubなどにはlib/
は含まれないが、手元でビルドした時にはlib/
ができるという状態を作り出せます。
公開するファイルをホワイトリストで指定する
逆にnpm publish
でライブラリをパッケージとして公開するときには、ES5に変換されたコードだけを公開したいです。
そのような場合はpackage.json
の"files"
フィールドに、ホワイトリストで含めたいファイル or ディレクトリを指定します。
lib/
だけを含めたいので以下のように書けます。
{
"name": "textlint-rule-helper",
"description": "Helper for textlint rule.",
"version": "1.1.2",
"homepage": "https://github.com/textlint/textlint-rule-helper/",
"repository": {
"type": "git",
"url": "https://github.com/textlint/textlint-rule-helper.git"
},
"main": "lib/textlint-rule-helper.js",
"files": [
"lib/"
]
}
package.json
、README.md
、LICENSE
といった標準的なファイルは、デフォルトで含まれるようになってるので書かなくても問題ありません。
公開後にnpm info <パッケージ名>
で何か含まれているかを知ることができます。
npm info textlint-rule-helper
で直接.tgz
ファイルを落としてみると含まれてることが分かると思います。
ホワイトリストで指定するとnpm packageのサイズが小さくなるというメリットもあるので、自然と取り入れやすいです。
npm publish
での公開方法については他の記事を参照してください。
test/
テストもES2015で書き、ここではテストフレームワークとしてMochaを使います。
テストファイルではES2015で書かれたsrc/
にあるコードをimportします。
import {RuleHelper} from "../src/textlint-rule-helper.js";
// package.json に "main": "lib/textlint-rule-helper.js", とある場合
import {RuleHelper} from "../"; // でも同じ意味になる
しかし、このままだとimport
文などES5ではないコードを実行することになるためテストコードもES5へ変換する必要があります。
テストにおいては変換方法として主に2つの方法があります
- ソースコードとテストコードを事前に変換(
npm run build
)して、ES5へ変換済みのJavaScriptとしてテストコードを実行する - ソースコードとテストコードを実行時に変換しながらテストする
ソースコードとテストコードを事前に変換
これは、Babelでソースコードやテストコードを変換してから、そのES5のコードに対してmochaで実行するという流れが必要になります。
gulpで表現すると以下の様な流れですね。
gulp.task('test', function () {
return gulp.src(TEST)
.pipe(sourcemaps.init())
.pipe(to5())
.pipe(sourcemaps.write())
.pipe(gulp.dest('./powered-test/'));
});
実際にgulpでやる場合は以下のリポジトリ等を参考にしてみるといい気がします。
この手法はテストコードも変換する設定を追加する必要があるため、確実ですが少し手間がかかります。
ソースコードとテストコードを実行時に変換
Node.jsにはモジュールを読み込む際にBabelなどの変換をランタイムに行える仕組みがあります。 これを利用することで、最小限の設定でテストコードとソースコードを変換しながらテストを実行できます。
次のようにmocha
の--require
引数に@babel/register
を設定します。
意味としては@babel/register
モジュールをロードして、JavaScriptファイルに対してはBabelの変換をランタイム時におこないます。
mocha test/ --require @babel/register
このようにするだけでES2015で書かれたテストコードを使ってテストできます。
テストの実行もnpm run-scriptとして実行できるようにscripts
に追加しておくと良いです。
次のように追加しておけば、npm run test
または npm test
(テストはショートカットが用意されている)でテストを実行できます。
...
"scripts": {
"build": "babel src/ --out-dir lib/ --source-maps",
"watch": "babel src/ --out-dir lib/ --watch --source-maps",
"test": "mocha test/ --require @babel/register",
"prepublish": "npm run build"
}
...
Tips
ES2015ではArrow Functionが使えるので、テストコードは以下のように書くことが出来ます。
describe("#getParents()", ()=> {
context("on Document", ()=> {
it("should return []", () => {
var text = "# Header";
var parents = [];
assert(parents.length === 0);
});
});
});
しかし、this
の扱いが違うのでテストケース間でthis.property
的なやり取りをしたい場合は気をつけましょう。
Document
追記(2018-10-06): 現在はES2015に対応したドキュメントツールもあります。
- documentationjs/documentation: beautiful, flexible, powerful js docs
- ESDoc - A Documentation Generator For JavaScript
- 75lb/jsdoc-to-markdown
このライブラリは小さいことを前提としてたので、README.md
にAPIの一覧をコード内のJSDocから生成して出しています。
75lb/jsdoc-to-markdownを使って出力したMarkdownをREADME.mdに貼り付けています。
エディタ
好きなエディタをつかって下さい。
ES2015の書き方
ES2015以降(ES2016, 2017なども含む)をベースに学ぶJavaScriptについての書籍を書いています。 基本的な構文などは大体カバーしているので参考にしてみてください。
構文だけに限ってみてもTemplate Stings、Spread operator、Computed Property Names、 class構文など、コードがシンプルに書きやすくなる部分が多いので、小さいライブラリを書くことで慣れておくといい気がします。
特にES2015 modulesはちょっと書き方が多いですが、仕様のシンタックスも固まったので慣れておきましょう。
- JavaScript Modules
- ECMAScript 6 modules: the final syntax
- LanguageFeatures · google/traceur-compiler Wiki
ライブラリで今までクラスっぽいものを書いて公開してるケースは、class構文を使うことでシンプルに書けるようになるのでおすすめです。
export class RuleHelper {
// コンストラクタ
constructor(ruleContext) {
this.ruleContext = ruleContext;
}
// 静的メソッド - RuleHelper.getHoge()
static getHoge(){
}
// プロトタイプのメソッド - ruleHelper.isFuga()
isFuga(){
}
}
ES2015に対応している書籍:
お知らせ欄
JavaScript Primerの書籍版がAmazonで購入できます。
JavaScriptに関する最新情報は週一でJSer.infoを更新しています。
GitHub Sponsorsでの支援を募集しています。