初めてJavaScript、ES2015(ES6)でちゃんとしたライブラリを作ったときの話

プログラミング
スポンサーリンク

こんにちは、なたでです、久しぶりの投稿です~!

前回からもう少しで半年経ちます。なぜこんなに時間が空いてしまったのかといいますと、npmにJavaScriptライブラリ登録を行ったためです。いったん公開してしまうと世界中の皆さんが使うということで、気になる点があるとすぐに直さないといけないという使命感・義務感が生まれまして、npmライブラリの修正に明け暮れてしまうことに・・・。

今回、JavaScriptでちゃんとしたライブラリを作って、npmでライブラリを公開するまでの、開発環境の準備の話をしたいと思います。作ったライブラリの話はまたいつかお話したいです。

はじめに

経緯をすこしだけ紹介したいと思います。

Windows環境で気軽にバッチ処理を作成するために、数年前からJScriptを使い始めました。batファイルは記載が面倒ですし、PowerShellは他OSでの利用を考えると応用が乏しく、そんなときにJScriptがちょうどよい思ったわけです。JScriptは、ほぼJavaScriptなので一度勉強すると、ブラウザやサーバー(Node.js)、アプリケーション(Electron)とか応用がききます。

その後、言語の理解を深めるためにJScript向けのライブラリを作成してはGitHubに上げていたのですが、JScriptの縛りのせいで、書き方の自由度がないというのが気になってきました。JScriptは、classが使えないのはもちろん、letconstも利用できなかったり、String.trim()とかもなかったり、当たり前の機能がないんです。というわけで、JScirptはばっさり切って、ES6の書き方でライブラリを作ることにしました。

そのうち作ったライブラリをnpmで公開したい欲が出てきまして、色々と今風の開発環境とかを勉強したりして、npm公開を目的としたライブラリ作成に移り、最終的にnpmで公開できるようになりました。やったね。

次章からnpmで公開のために準備した、ES2015(以下、ES6)のライブラリ開発環境の構築について解説を始めます。

前準備

JavaScriptでの開発ということで、npmGitHubVisual Studio Code(以下VSCode)を利用します。インストール方法などは、以前書いた「JavaScript開発環境をNetBeansからVisual Studio Codeに切り替えた話」の記事を参考にするとよいです(今回の記事と一部重複します)。今回はこれらの環境が準備できているという前提でお話ししたいと思います。

npmの設定

npmのパッケージ登録及び、npmでツールをインストールするためには、パッケージ情報(package.json)を作成する必要があります。

以下のコマンドを打つことで、対話形式でパッケージ情報を作成できます。

npm init

こんな感じで作れます。

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (banner) ライブラリ名
version: (1.0.0) 1.0.0
description: ~するライブラリです
entry point: (index.js) ./build/index.js
test command: jest
git repository: https://github.com/xxx/yyy.git
keywords: math
license: (ISC) MIT
About to write to ...\package.json:

{
  "name": "ライブラリ名",
  "version": "1.0.0",
  "description": "~するライブラリです",
  "main": "./build/index.js",
  "scripts": {
    "test": "jest"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/xxx/yyy.git"
  },
  "keywords": [
    "math"
  ],
  "author": "natade-jp <natade3@gmail.com> (https://www.natade.net/)",
  "license": "MIT"
}


Is this OK? (yes) yes

あとでpackage.jsonを直接編集して変更もできるので、適当に入力してもかまいません。

npmのよく使うコマンド

いくつかコマンドを紹介します。

外部ライブラリのインストール方法

パッケージを実行するのに必要な外部ライブラリをインストールする。(package.jsonDependenciesに追記される)

npm install --save xxx

パッケージを開発するのに必要な外部ライブラリをインストールする。(package.jsondevDependenciesに追記される)

npm install --save-dev xxx

アンインストールする場合は、installの部分をuninstallに変えて同じコマンドを実行するだけでよいです。なお、インストールしたデータは、プロジェクトフォルダ直下のnode_modulesフォルダに保存されます。

外部ライブラリのインストール情報

インストールしたライブラリを確認する。

npm list --depth=0

外部ライブラリのインストール情報の復元

package.jsonを元に外部ライブラリのインストールを行います。

npm install

作成したパッケージの公開

作成したライブラリなどを外部に公開する(ライブラリが完成したら公開します。1度公開すると消すのが大変なので注意)

npm publish

npmでのバージョンの付け方について

x.y.zのように3つ数値を付けれます。それぞれの数値は次のように意味を持っています。

  • x … 前回と互換性がなくなる場合に1を上げる。
  • y … 互換性を保ちつつ、機能が増える場合に1を上げる。
  • z … 互換性を保ちつつ、不具合を修正した場合に1を上げる。

github及びnpmのアップロード不要リスト

作成したライブラリは、GitHubで管理し、最終的にnpmで公開という流れになりますが、不要なデータをアップロードしないように設定が可能です。

.gitignore

GitHub専用のアップロード不要リストです。例えば、node_modulesフォルダなどを登録しておきます。基本的にpackage.jsonさえあれば、復元できるものはアップロードは不要です。

以下は設定例です。それぞれ何を表しているかは割愛します。

/node_modules/
/npm-debug.log
/out/
/docs/ast/
jest.config.js
.npmrc
.DS_Store
Thumbs.db
*.bak

.npmignore

npm専用のアップロード不要リストです。このファイルがない場合は、.gitignoreが利用されます。

npmには、基本的にビルドしたデータのみを公開となります。以下は設定例です。それぞれ何を表しているかは割愛します。

/node_modules/
/npm-debug.log
/src/
/out/
/html/
/docs/
/scripts/
/.vscode/
jest.config.js
.npmrc
.babelrc
.travis.yml
.esdoc.json
.eslintrc.json
.gitignore
.DS_Store
Thumbs.db
*.bak
tsconfig.json

ES6の統合開発環境

VSCodeはプラグイン形式で色々な機能を拡張できるのですが、とくにESLintというリアルタイムの静的解析ツールと、型推論機能が強力です。そのため、これから始める場合はVSCodeでの開発が良いと思います。ここでは、VSCodeで利用できる静的解析ツールESLintを紹介した後、型推論機能の紹介をしていきます。

静的解析ツール

今回はESLintの紹介になりますが、この他にも色々な静的解析ツールがリリースされているので紹介します。

ツール名解説
JSLint2007年あたり有名になったJavaScript用の静的解析ツール。オライリーの「JavaScript: The Good Parts」の著者が作成したツール。
JSHintJSLintのルールが厳格すぎる問題を解決するためにフォークされて、変更を加えたJavaScript用の静的解析ツール。2011年に登場。
ESLintES6に対応したJavaScript用の静的解析ツール。2013年に登場。全てのルールでオンオフの切り替えができて、カスタマイズ性があり自由度が高い。
JSCS2013年ごろに登場。ESLintにマージされている。

ESLint

ESLintは、2013年ごろに登場したJavaScriptのES6向けの静的解析ツールです。

インストール

以下のようにコマンドを実行することでインストールが行えます。

# For error check of ES6.
npm install --save-dev eslint

設定方法

インストール後は、プロジェクトフォルダ直下に置いたJSON形式の.eslintrc.jsonが設定ファイルとして利用されます。私は以下のような設定を使っています。

{
	"env": {
		"browser": true,
		"es6": true,
		"node": true,
		"jest": true
	},
	"extends": "eslint:recommended",
	"parserOptions": {
		"ecmaVersion": 2015,
		"sourceType": "module"
	},
	"rules": {
		"indent": [
			"error",
			"tab",
			{
				"SwitchCase": 1
			}
		],
		"linebreak-style": [
			"error",
			"unix"
		],
		"quotes": [
			"error",
			"double"
		],
		"semi": [
			"error",
			"always"
		],
		"no-constant-condition": 2,
		"no-unused-vars": 1,
		"no-console": 1,
		"no-var": 2,
		"prefer-const": 2,
		"no-param-reassign": 2
	}
}

細かく設定できるのがとてもよいです。「1」や「2」と書いてある部分は、エラーとするか、注意とするかです。エラーまでは求めないけども、注意として出したい場合に「1」としています。

設定内容は以下の通りです。

パラメータ名設定内容
env
  • ブラウザ用のグローバル変数を許可。
  • ES6用の書き方を許可。
  • Node.js用のグロバール変数を許可。
  • Jest用のグロバール変数を許可。
extendsESLint用の推奨設定を一括設定(eslint:recommended
parserOptionsESLintで使用しているパーサーの設定。
indent
  • インデントはタブを使用する。
  • Switch文でもインデントを使用する。
linebreak-style改行コードは、LFとする。
quotes文字列を表す場合は、ダブルクォーテーションで囲む。
semi文章の最後はセミコロンとする。
no-constant-condition条件に、truefalseを直接入れている場合は注意を出す。
no-unused-vars宣言はしているが使用していない変数は注意を出す。
no-consoleコンソール文がある場合は注意を出す。
no-varvarは使用してはいけない。letconstを使用する。
prefer-const更新しない変数は、constを使用する。
no-param-reassign引数に入った値を変更しない。

型推論機能

型推論機能を入れると型が明確になるため、文字列を引数に取る関数で数値を入れるとエラーが発生し、コーディングミスにすぐに気が付くことができます。また、オブジェクトの型を定義しておけば、その型の中の値をすぐに引き出すことができるため、コーディング速度が上がります。

なお、型推論機能は、ソースコード中に型情報に関するコメントを記載することで利用できるのですが、この記載方法は後に紹介する自動ドキュメント作成と密接に関わってきます。というのも、自動ドキュメント作成をする場合にも、ドキュメント作成用のコメントを記載するのですが、書式が型推論機能と同じであるためです。

型推論機能の有効化方法は簡単で、以下の2通りの方法があります。

  • ソースコードの先頭に//@ts-checkと記載する。
  • jsconfig.jsonファイルを設置する。

以下、詳細を説明していきます。

ts-check

上記で解説した通り、ソースコードの先頭に//@ts-checkと記載するだけで型推論機能が有効化されます。

jsconfig.json

JavaScriptがおいてあるフォルダ内にjsconfig.jsonという名前のJSON形式の設定ファイルを設置します。

{
	"compilerOptions": {
		"charset": "utf8",
		"newLine": "LF",
		"lib": ["es2015", "dom"],
		"target": "es2015",
		"module": "es2015"
		"checkJs": true,
		"noImplicitAny": true
	},
	"exclude": [
		"node_modules",
		"**/node_modules/*"
	]
}

JSONファイルの設定内容は、上記の通りです。

この設定ファイルがあるディレクトリ、及びその配下のディレクトリの全てでのスクリプトで型推論機能が有効化されます。もしプロジェクトフォルダの配下に設置してしまうと、本来型推論機能が不要なファイルまで有効化してしまうため、設置する際は注意してください。

補足ですが、TypeScirpt用の設定ファイルtsconfig.jsonでもJavaScriptの設定が可能です。ただ、今回はJavaScript専用のライブラリのためjsconfig.jsonで設定しています。

ファイルの拡張子について

今回はES6での開発なので、ES6専用の拡張子mjsを使用する前提で環境設定などを話していきたいと思います。ただし、開発中はjsの拡張子をおすすめします。この理由は、2019年7月現在、VSCodeが型推論機能を有効化した上で拡張子mjsの外部ファイルのインポートが行えない問題があるためです。

jsconfig paths not working for .mjs files #76576

1つ問題として、拡張子がjsのままだと、Node.jsでES6として実行ができないため、別途配布用の専用ファイルを用意するなど工夫が必要です。

以下、参考として、ES5以前とES6の拡張子による、Node.js及びブラウザの動作の違いをまとめました。

ES5以前

Node.jsでは、拡張子をjsにした上で、以下のコマンドで実行する

node *.js

Node.jsにおいて、上ライブラリを読み込む場合は、以下のコマンドのいずれかで対応する。

const x = require("x");
import x from "x.mjs";

ブラウザ上で実行する場合は、上記のスクリプト内でのライブラリの読み込みはできないため、HTML上でUMD形式のライブラリをscriptタグで実行する。

<script src="./library.umd.js" charset="utf-8"></script>
<script src="./main.js" charset="utf-8"></script>

ES6

Node.jsでは、拡張子をmjsにした上で、以下のコマンドで実行する

node --experimental-modules *.mjs

ライブラリを読み込む場合は、以下のコマンドで対応する。

import x from "x.mjs";

ブラウザ上で実行する場合は、type="module"を付けて実行する。

<script type="module" src="./main.mjs" charset="utf-8"></script>

拡張子「.mjs.js」について

暫定対応として、ファイル名をmylib.mjs.jsのように二重でつけておくと、スクリプト上からmylib.mjsでロード可能かつ、VSCodeでも読み込める方法があります。ただしこの場合は、ブラウザで動作させた場合にファイルが存在せずエラーを発生させてしまいます。Node.jsのみでの開発に限っていえば、このような方法も策としてはありかもしれません。

mjsの拡張子をWebサーバーに設置する場合について

補足ですが、ブラウザに限った話でいえばファイルの拡張子がどうであれ、サーバーから受け取るMIMEタイプが正しければ、JavaScriptとして実行できます。

サーバーのMIMEタイプの設定として例えば、Apacheサーバーであれば.htaccessファイルに以下の行が必要となります。

AddType text/javascript js mjs

ES6自動ドキュメント作成環境

重要なのがドキュメント作成です。例えばライブラリを作成した後に、ライブラリの使い方を説明するドキュメントを作成する必要がありますが、自動ドキュメント作成用ツールを導入することで、作成する手間を省けます。さらに先ほど説明した、型推論機能と併用することができるため、必ず書いていった方がいいでしょう。

ドキュメント作成ツール

今回、2015年に登場したES6向けの自動ドキュメント作成ツールのESDocを利用します。

色々な自動ドキュメント作成ツールがあるのですが、どれも書き方は、Javadocのコメントの書き方に似ています。その他のドキュメント作成ツールについても紹介します。

ツール名解説
JSDocJava用のJavadocに似た構文を書くことでドキュメントを自動生成できるツール。開発停止。1999年以前からある。
JsDoc ToolkitJSDocのバージョン2系。2007年あたりに登場。動作にはJavaが必要。2011年あたりに開発停止し、JSDoc3が開発されている。
JSDoc3JSDocのバージョン3系。2011年あたりに登場。2015年11月のJSDoc 3.4.0からES6に対応しているが、export defaultには正式対応していないためjsdoc-export-default-interopが必要。
ESDocES6向けのドキュメント自動生成ツール。ドキュメントカバレッジなども計算できる。作成したドキュメントが綺麗。ベータ版だがドキュメントのホスティングサービスも存在。2015年に登場。
Documentation.jsES6向けのドキュメント自動生成ツール。2015年に登場。

ESDoc

ESDocのメリットは、何も拡張機能を入れなくても綺麗なドキュメントを作成してくれたり、ドキュメントカバレッジを表示してくれたりする点です。JSDoc3はテーマに対応しており、テーマを入れることで綺麗なドキュメントも作成できると思いますが、色々と設定が大変そうですし。ESDocを利用することとしました。

インストール

以下のようにコマンドを実行することでインストールが行えます。

# For document creation of ES6.
npm install --save-dev esdoc
npm install --save-dev esdoc-standard-plugin

設定方法

インストール後は、プロジェクトフォルダ直下に置いたJSON形式の.esdoc.jsonが設定ファイルとして利用されます。私は以下のような設定を使っています。

{
	"source": "./src/",
	"destination": "./docs/",
	"includes": ["\\.m?js$"],
	"excludes": ["\\.test\\.m?js$"],
	"index": "./README.md",
	"outputAST": false,
	"plugins": [
		{
			"name": "esdoc-standard-plugin",
			"option": {
				"unexportedIdentifier": {"enable": false}
			}
		}
	]
}

注意点としては、2019年7月現在デフォルトの設定ではES6のファイルの拡張子であるmjsに対応していないという点です。そのため、上記のようにmjsを対象とさせる必要があります。また、AST(Abstract Syntax Tree)ファイルの作成をオフといます(デフォルトの設定はオン)。astファイルは、ソースコードのパース情報であり、数十MBにもなります。リポジトリ管理は不要のため、もし有効にするなら.gitignore内にアップロード禁止として必ず設定をして下さい。

実行方法

設定が終われば、以下のコマンドでドキュメントを作成できます。

npx esdoc

VSCode型推論/ESDocのコメントの書き方の例

ここでは、ソースコード中に記載するドキュメント/型推論用のコメントコードの記載例を紹介します。

/**
 * ~する関数
 * @param {number} x - 引数1
 * @param {string} y - 引数2
 * @returns {string}
 */
static myfunc(x, y) {
	return "test";
}

上記のような感じで関数名の上に「/**」を記載しコメントを書いていきます。VSCodeを使用してれば、関数の上の行で「/**」を記載してEnterを押すだけで、「@param」と「@returns」まで自動で作成されるので、それほど苦ではありません。

VSCode型推論/ESDocの型の書き方の例

以下はよくある書き方の例です。

@param {number|string} x - 数値か文字列を引数とする。
@param {Array} x - 配列を引数とする。
@param {?Object} x - オブジェクトを引数とするが、nullを指定してもよい
@param {!Object} x - オブジェクトを引数とする。nullを指定してはいけない
@param {?(number|string)} x - 数値か文字列あるいは、nullを指定しても良い。
@param {number} [x] - 数値を引数とするが、省略してもよい。
@param {number} [x=10] - 数値を引数とするが、省略してもよい。省略した場合はデフォルトを10とする。
@param {{A: number, B: string}} x - AとBをメンバに持つオブジェクトを引数とする。
@param {Array} x - 数値型の配列。
@param {Object<string, number>} x - キーを文字列、値を数値としたハッシュオブジェクト。
@param {...number} x - 数値型の可変引数
@param {typeof ClassX} x - ClassXの型を引数とする。

型自体の定義について

型自体を定義することも可能です。

/**
 * 型のみの情報
 * @typedef {Object} MyObject
 * @property {number} [x] 省略していい数値
 * @property {number} y 省略してはいけない数値
 */

このように記載しておくと、MyObject自体が型として引数の設定に利用が可能となります。

ESDocの型定義の注意点

以下はESDocにおいて、注意する必要がある記載例です。「x」の書き方はESDocでエラーが発生します。

  • オブジェクトの定義
x @param {a : ?(number|string)} x - aには数値か文字列あるいは、nullを指定しても良い。
o @param {a : (?number|?string)} x - aには数値か文字列あるいは、nullを指定しても良い。
  • 関数の定義
x @param {function(number, string): boolean} func - 第1引数には数値、第2引数は文字列、戻り値はboolの関数
o @param {function(a: number, b: string): boolean} func - 第1引数には数値、第2引数は文字列、戻り値はboolの関数

特に、関数の定義についてはVSCodeの型推論機能と互換性がないため併用する場合に注意が必要です。

  • 他のファイルに記載してある型情報の利用
x @type {import("./aaa/bbb.js").MyType}
o -

他のファイルに記載してある@typedefで定義した型情報は、VSCode型推論機能の場合は、import("hoge.js")に続けて、.MyTypeとすることで利用できます。ただ、この方法は、ESDocで利用できず、現状代替え案が分かりません。

VSCode型推論とESDoc関数定義の両立策

funcitonの書き方はVSCode上で型推論を利用したいため、型推論の書き方を重視した方がよいと思います。ただ、この場合は上記の通りESDocとの互換性が失われます。私の場合は、暫定的に以下のようにESDocのDocBuilder.jsファイルを改造し、引数名を自動追加するようにしております。

const is_function = typeName.match(/function *\((.*?)\)(.*)/);
let typeName2 = "";
if(is_function) {
	let prm_num = 1;
	const prm_rep = function(text) {return text + " prm" + (prm_num++) + ":"}
	typeName2 = typeName.replace(/\(|,/g, prm_rep);
}
matched = typeName2.match(/function *\((.*?)\)(.*)/);

また、他のファイルに記載してある型情報の利用でエラーが発生しないように、以下のコードもDocBuilder.jsファイルに追加しています。

typeName = typeName.replace(/import\([^)]*\)\./g, "");

詳細は、GitHubのコードを見てください。

VSCode型推論/ESDocでの改行やリストの記載方法

引数の「@param」と戻り値の「@returns」は使えませんが、説明欄では改行とリストが使用できます。

改行は、2回改行で行えます。「<br>」タグを使うというのもありますが、VSCodeの関数のヘルプポップアップ表示では、改行タグは対応していません。

/**
 * 1行目
 * 
 * 2行目
 * @param {number} x - 引数1
 * @param {string} y - 引数2
 * @returns {string}
 */
static myfunc(x, y) {
	return "test";
};

リストを使用する場合は、先頭にハイフン-を使用してください。

/**
 * ~する関数。
 * - ~して下さい。
 * - ~に注意。
 * @param {number} x - 引数1
 * @param {string} y - 引数2
 * @returns {string}
 */
static myfunc(x, y) {
	return "test";
}

ES6パッケージング環境

次は、ES6のパッケージング環境を紹介します。JavaScriptなのでコンパイル等は不要ですが、公開用のファイルは通常パッケージ化します。ES6でプログラムを書くと、Javaのように1クラス1ファイルで書いたりするのですが、それをWeb上に設置した場合ファイル数分だけダウンロードが発生するために、1ファイルにまとめて更に内部変数を短く、コメントは削除といったことをするのです。

パッケージ化ツール

以下のようなツールがあります。

ツール名解説
Browserifynode.jsのモジュール機能を利用したjsをまとめて、モジュール未対応のブラウザでも動かすことができるようにするツール。2011年に登場。
webpack複数のモジュールで構成されたjsやcssなどのファイルを、1つのファイルに固めることを目的としたツール。2012年に登場。
rollup.jsES6機能で記載したモジュールjsをライブラリとして公開する場合に1つのファイルに固めるという目的のツール。2016年あたりに登場。
ParcelAdobeのエンジニアが中心になって開発した複数のファイルを一緒にするツール。2017年あたりに登場。webpackのような複雑な設定が不要。

rollup.js

ここでは、rollup.jsというES6用のパッケージ化ツールを使用して、公開用のライブラリファイルを作成方法を紹介したいと思います。

インストール

以下のようにコマンドを実行することでインストールが行えます。

# Packaging the script described in the ES6.
npm install --save-dev rollup
npm install --save-dev rollup-plugin-buble
npm install --save-dev rollup-plugin-uglify
npm install --save-dev rollup-plugin-uglify-es

今回はプラグイン形式として色々インストールします。まず主となるのが、rollupでモジュールで記載したコードのimport/export文をまとめるツール。Bubleは、ES6で記載したコードをブラウザで動くスクリプトへのトランスコンパイラです。uglifyはJavaScirpt用、uglify-esはES6用の難読化/最小化用のツールです。

補足ですが、Bubleに似た名前の、Babelというのもあります。こちらは、ES6に対応しておらず、ES6に対応したものがBubleとして作られています。

設定方法

rollupの設定は、プロジェクトフォルダにrollup.config.jsを設置するか、実行時の引数で設定ファイルを渡します。設定ファイルは、JavaScript形式であり、以下のような形でスクリプトの内部でJSONデータを定義し、オブジェクト変数をexport defaultを使用して出力するようにします。

const data = {
	input: "src/main.js",
	output: {
		file: "bundle.js",
		format: "cjs"
	}
};

export default data;

私の場合は、以下のようにスクリプトを作成することで、ES6用/ES5用、難読化のあり/なし、計4パターンを作成するようにしました。注意点として、ES5用ライブラリで難読化させる場合は、uglifyJS()ではなくuglifyJS.uglify()のようにメソッド名まで設定しないといけません。

import buble from "rollup-plugin-buble";
import uglifyJS from "rollup-plugin-uglify";
import uglifyES from "rollup-plugin-uglify-es";

/**
 * 公開用ファイルの設定データを作成
 * @param {string} moduleName - ライブラリ名
 * @param {string} input_name - 入力となるES6のライブラリのトップファイル名
 * @param {string} output_name - 出力するファイル名
 * @param {boolean} isES6 - ES6に対応したライブラリとするか否か
 * @param {boolean} isUglify - コードを最小化させるか否か
 */
const createData = function(moduleName, input_name, output_name, isES6, isUglify) {
	const data = {};
	data.input = input_name;
	data.output = {};
	data.output.file = output_name;
	data.output.format = isES6 ? "esm" : "umd";

	if(isES6) {
		if(isUglify) {
			data.plugins = [
				uglifyES()
			];
		}
	}
	else {
		data.output.name = moduleName; // ES5 必須
		data.plugins = [
			buble()
		];
		if(isUglify) {
			data.plugins.push(
				uglifyJS.uglify()
			);
		}
	}

	return data;
};

const data = [];

data.push(createData("NatadeLib", "./src/NatadeLib.js", "./build/NatadeLib.module.mjs", true, false));
data.push(createData("NatadeLib", "./src/NatadeLib.js", "./build/NatadeLib.umd.js", false, false));
data.push(createData("NatadeLib", "./src/NatadeLib.js", "./build/NatadeLib.module.min.mjs", true, true));
data.push(createData("NatadeLib", "./src/NatadeLib.js", "./build/NatadeLib.umd.min.js", false, true));

export default data;

なお上記の方法でライブラリは作成できますが、難読化したファイルはライブラリ名やライセンス情報が消えるため、私はバッチ内で自動でくっつけるように設定しています。

実行方法

以下のコマンドでビルドが始まります。

npx rollup

設定ファイルを引数で渡したい場合は以下のようになります。

npx rollup -c "./scripts/rollup.config.js"

npm公開用パッケージの作り方

npmで一般公開する場合は、ES6形式ではなくCommonJS形式を使うほうがよいです。というのも、rollupで出力したUMD形式は、VSCodeで読み込んだ場合にJSDocの注釈が正しく読み込めない問題があります。

CommonJS形式を作る場合は、ES6形式で出力した後に最後の行を

export default MyLib;

から

module.exports = MyLib;

に変更することで作成が可能です。

ES6テスト環境

次はテスト環境について紹介します。テスト環境を用意しておかないと、リファクタリングする度に必要な影響確認が非常に手間となるため、最初のうちに導入することをお勧めいたします。

テストツール

テストツールをいくつか紹介します。

ツール名解説
QUnitJavaScript用の単体テスト自動化用のフレームワーク。2008年に登場。
Sinon.jsJavaScript用のMock用のライブラリ。2010年に登場。
mocha設定したテストプログラムを実行するテストランナー、テストフレームワーク。2011年に登場。
ChaiJavaScriptでテストのアサーション用のAPIを提供。2012年あたりに登場。
Jasminテストランナー(mocha)とアサーション(chai)が利用できるテストツール。2012年あたりに登場。
istanbulJavaScriptでテストのカバレッジを調べる。2013年に登場。
JestFacebook製のテスト用パッケージ。mocha、chai、Sinon.js、istanbul といった色々な機能がこの1つのパッケージで利用できる。2014年あたりに登場。

Jest

ここでは、Jestを使うこととしました。上記の解説に書いてあるように、色々インストールしなくてもこれ1つで事足りるのが非常によいです。

インストール

インストールは以下のように実行します。

# For unit test of ES6.
npm install --save-dev jest
npm install --save-dev babel-jest
npm install --save-dev babel-plugin-transform-es2015-modules-commonjs
npm install --save-dev @types/jest

今回、Jest用のプラグインとして、トランスコンパイラのbabelプラグインと、babel用のES6対応プラグインも一緒に入れます。これはJestがES6に対応していないためです。

@types/jestは、型情報のインストールです。この情報をインストールしておくと、Jest用の関数を使用しても型推論機能でのエラーが発生しなくなります。

設定方法

Jest用の設定は、npmの設定であるpackage.json内に記載することが可能です。プロジェクトフォルダの直下にjest.config.jsがあれば、そっちが優先されます。以下は設定例です。

"jest": {
	"verbose": true,
	"rootDir": "./src",
	"moduleFileExtensions": [
		"js",
		"mjs"
	],
	"testMatch": [
		"**/__tests__/**/*.?(m)js?(x)",
		"**/?(*.)(spec|test).?(m)js?(x)"
	],
	"transform": {
		"^.+\\.(js|mjs)$": "babel-jest"
	}
}

内容としては、ESDocのようにmjsを読み込む設定、テストファイルの設定を行っています。上記の設定の場合、MyLib.mjsというES6のファイルのテストファイルを作りたければ、MyLib.test.mjsのように拡張子の前にtestという名前をつけたファイルを作るということになります。

注意点としては、transformを使用して、トランスコンパイラであるbabel-jestを実行するように設定しています。トランスコンパイラの設定は、プロジェクトフォルダ直下の.babelrcファイルで行います。.babelrcファイルは以下のような内容を記載して、JestをES6に対応させます。

{
	"env": {
		"test": {
			"plugins": [
				"transform-es2015-modules-commonjs"
			]
		}
	}
}

実行方法

jest.config.js.babelrcの設定後に、以下のコマンドでテストを行えます。

npx jest

テストファイルの作成方法

テストファイル(*.test.mjs)は、次のように記載します。

test("test case #1", () => { expect(1+1).toBe(2); });

expectにはテストしたい関数、toBeには期待したデータを入れます。

TravisCI

Jestの設定が終わった後は、TravisCIというオンラインのテスト支援サービスを利用の設定をするのが一般的です。このサービスは、2011年あたりに登場したもので、GitHubアカウントで簡単にアカウントを作成でき利用が可能です。登録をしておくことで、GitHubのプロジェクトページと連動し、アップロードする度に自動でテストが実行されるようになります。

ここでは、詳しく解説しませんが、簡単お手軽なので是非登録しておきましょう。

設定方法

設定ファイルは、プロジェクトフォルダ直下の.travis.ymlとなります。中身は以下のように設定しています。

language: node_js
node_js:
  - "8"

# safelist
branches:
  only:
  - master

型定義ファイル(d.ts)の作成

型定義ファイルというのは、パッケージを公開後に型推論機能を利用したい場合に使うファイルです。通常、パッケージは最小化ファイルなどコメントが省略されており、このような場合に型ファイルがないと、利用しづらい形になります。

正直、実は私自身この辺はまだ勉強中でして、JavaScriptのライブラリ用で型定義ファイルを準備した場合、その型定義ファイルをどのように公開したらよいのか。また作成した型ファイルをインポートするにはどのようにしたらいいのか分からないのが現状です。誰かそのあたり詳しい方がいらっしゃると助かるのですが、教えたいという型がいらっしゃいましたらTwitterの方で優しく教えていただけると助かります。

一旦その話はおいておきますが、ここでは型定義ファイルの作成について調査したので、説明したいと思います。

自動で型定義ファイルを作成する

型定義ファイルの自動作成方法はいくつかあります。

dtsmakeの方は試したのですが、ES6に対応できていないように見えました。少なくとも私のライブラリはうまく作れませんでした(;;)。今回はtsd-jsdocを使用していきます。

tsd-jsdoc

インストール

インストール方法は、以下のようになります。

# For typescript type definition creation.
npm install --save-dev jsdoc
npm install --save-dev jsdoc-export-default-interop
npm install --save-dev tsd-jsdoc

JSDocはJSDoc用のコメントからドキュメントを自動作成するツールです。tsd-jsdocは、そのプラグインとして動作する型定義ファイルを作成するツールです。jsdoc-export-default-interopは、JSDocにES6を対応させるプラグインです。

設定方法

設定ファイルは、json形式で用意し、実行時に引数としてファイルを指定します。設定は以下のようになります。

{
	"tags": {
		"allowUnknownTags": true
	},
	"source": {
		"includePattern": ".+\\.m?js$",
		"excludePattern": "\\.test\\.m?js$"
	},
	"plugins": ["./node_modules/jsdoc-export-default-interop/dist/index"],
	"templates": {
		"cleverLinks": false,
		"monospaceLinks": false,
		"default": {
			"outputSourceFiles": false
		}
	},
	"opts": {
		"template": "./node_modules/tsd-jsdoc/dist"
	}
}

設定では、.test.ファイルは含めないようにし、プラグインにはES6対応プラグインを設定。JSDocのテーマ情報にtsd-jsdocを設定させます。

実行方法

設定ファイルと、ソースコードがあるディレクトリを指定して実行すると、outフォルダにtypes.d.tsファイルが作成されます。

npx jsdoc -c "./scripts/.dts.json" -r "./src/"

出力データの修正

types.d.tsファイルは、このままだ不完全な状態になります。なぜか戻り値がanyとなってしまっている場合があるためです。私はこれを防止するために、以下のようにJSDocのコメントに記載されている戻り値に、強制的に置換するスクリプトを実行しています。

// 戻り値の補正
// 戻り値が any で終わっているものは解析エラーの可能性があるため、returns の情報を使用する
{
	const dts_text_line = dts_text.split("\n");
	for(let i = 0; i < dts_text_line.length; i++) {
		const line = dts_text_line[i];
		if(!line.endsWith(": any;")) {
			continue;
		}
		// 戻り値がanyで終わっているものは解析エラーの可能性がある。
		let returns = null;
		for(let j = i - 3; j < i; j++) {
			// {} の入れ子について
			// 1重 (\{[^{]*\})
			// 2重 (\{[^{]*((\{[^{]*\})*[^{]*)\})
			// 3重 (\{[^{]*((\{[^{]*((\{[^{]*\})*[^{]*)\})*[^{]*)\})
			// 3重まで対応
			if(/(\s*\*\s*@returns?\s*)(\{[^{]*((\{[^{]*((\{[^{]*\})*[^{]*)\})*[^{]*)\})/.test(dts_text_line[j])) {
				const match = dts_text_line[j].match(/(\s*\*\s*@returns?\s*)(\{[^{]*((\{[^{]*((\{[^{]*\})*[^{]*)\})*[^{]*)\})/)[0];
				const with_block = match.replace(/(\s*\*\s*@returns?\s*)/, "");
				returns = with_block.replace(/(^{)|(}$)/g, "");
				break;
			}
		}
		if(returns === null) {
			continue;
		}
		// 2行前がreturnコメントなら、returnコメントを採用
		dts_text_line[i] = line.replace(/: any;$/, ": " + returns + ";");
	}
	dts_text = dts_text_line.join("\n");
}

また、外に見せてはいないクラス名が、他の一般的なクラス名に衝突しないように、クラス名に衝突防止用の装飾をしています。

{
	const types = [
		"Matrix",
		"Complex",
		"BigInteger"
	];
	for(let i = 0; i < types.length; i++) {
		const word = types[i];
		let reg = null;
		// 文字列を装飾する
		reg = new RegExp("([\\W])(" + word + ")([\\W])", "g");
		dts_text = dts_text.replace(reg, "$1_$2_$3");
	}
}

詳細は、私のGitHubの型定義作成用スクリプトを確認してみてください。

型定義ファイル(d.ts)を使用する

基本的にJSDoc形式のコメントがついたJavaScriptをインポートすれば、自動で型定義が実行できます。ただ、例えばES5以前などで、HTML上で何らかのライブラリをロードして、別ファイルでそのライブラリの機能を利用するといった場合に、型情報を拾うことができない場合があります。

解決策として、JavaScriptのコード上に型定義ファイルを読み込むコードを入れる方法があります。

/// <reference path="./mylib.d.ts" />

上記のように、JavaScriptの上部にreference pathを設定して、相対パスを設定することで型定義ファイルを読み込みできます。

さいごに

お疲れ様でした。

今回は、私が挑戦したJavaScriptライブラリの開発環境をまとめて紹介しました。ES6で開発した場合に、この記事がお役に立てるとよいです。ただ、まだまだ型定義ファイルについては分からない部分があったり、VSCodeのパスの動作など私が理解できていない部分がたくさんあるため、分かり次第記事の修正などもしていけたらいいかなと思っています。

次回の予定は、開発環境ではなく今回作成したライブラリの機能についての紹介記事を作りたいと思います。

長くなってしまいましたが、最後まで読んでいただきありがとうございました!

タイトルとURLをコピーしました