ひびのログ

日々ではないけどログを出力していくブログ

npm パッケージ作成奮闘録 ~Browserify を添えて~

先日作成した npm パッケージについて、所感等まとめておこうと思います。

npm パッケージ作りました

詳しくはこちらの Qiita のページを参照してください。

qiita.com

内容としては、「insertAdjacentHTML」を使いやすくしよう! ということで作成しました。 一番の狙いは、文字列ではなく関数として定義しておくことにより、エディタによる補完の恩恵を受けられるようにすることでした。 あとは文字列を覚えるのが面倒だったので、リファレンス的にも使えるかな、と思っていました。 内容的には簡単なラッパーなので、上記の理由があってもライブラリ作るほどでもないかな?とは思ったのですが、練習の意味も込めて作ってみました。

でも初回リリース時は利用できませんでした

リリースしたはいいものの、結果を見たくて npm install したら使えない…… おいおい嘘だろ頼むよと、仕事そっちのけで原因の検証を行いました。いや、仕事はちゃんとしました、はい。 すぐに原因の特定と修正ができたのが幸いでした。

原因は Browserify

調べた結果、ビルドの際 Browserify を使っていたからのようです。 ちゃんと Browserify のことを知っている人からすれば「そりゃそうだ」って感じですよね。

使ってしまった理由としては、他のプロジェクトで TypeScript を使って開発していたときの package.json を流用したからです。 コピペがまずいほうに影響してしまった結果です。反省してます。

ちゃんと export していたにもかかわらず、そのビルドのタイミングで使えなくなってしまいました。

そもそも Browserify ってなんだろう

間違えてしまったものは仕方ないので、そもそも Browserify はどんなことをしているのか見てみましょう。

ブラウザで動かない AltJS をトランスパイルする Babel や TSC(TypeScript コンパイラ)とは違い、簡単に言えば、import, export, require を解決してファイルを繋げてくれるツール、という認識です。 ただそれだけではなく、プラグインで AltJS をトランスパイルしたりしてくれます。今回使用した tsify なんかもそうです。 また、ファイルを繋げた後に、グローバルスコープ汚染を防ぐための処理が入ります。 要するに処理全体を即時関数で包むというアレです。

(function () {
    // 処理
})();

今回発生したエラーは、この処理が走ったがために、外部へexportされなかったのではないかと思います。

実際に Browserify でトランスパイルされたJSはどうなっている?

勉強のため、トランスパイルされたコードを見てみます。 簡単にするために、今回のモジュールの一部を抜き出しています。 (実際のソースが見たい方はこちら

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
exports.__esModule = true;
function insertHTMLBeforeBegin(element, text) {
    element.insertAdjacentHTML("beforebegin", text);
}
exports.insertHTMLBeforeBegin = insertHTMLBeforeBegin;
;
},{}]},{},[1])

↑を見やすいように整形したのが↓です。

処理が整形後の 32 ~ 38 行目に書かれています。 31 行目を見ると、無名関数に包まれています。(function (require, module, exports)) 1 行目の関数 e の第一引数として渡されているようです。

そのため、insertHTMLBeforeBegin を代入している exports 変数が外部から見えないので、このファイルを import できないということがわかります。

再発防止のために

今回やらかしたことはもうどうしようもないので、今後このようなミスを避けるためにはどうしたらいいか考えてみます。 ぱっと思いつくのは 3 つありました。

まず、作ったあとにちゃんと検証していれば、こんなことにはならなかったはずです。 時間がない朝にデプロイしたのが問題でした。 デプロイ後に、ちゃんと自分で使えるか検証した上で告知(もしくは今回のように Qiita への投稿)を行うべきでした。

次に、使うツールが何をするのかをしっかりと把握しておくことが大切だと感じました。 今回であれば、Browserify とそのプラグインたち、そして npm, Github, TypeScript あたりです。 特にコードをいじるツールについては、しっかりと理解をしておくことが大切だと思います。 実際に動かしてみたときにエラーとなって動かないとなったときに、どこが原因か特定するのに役立つはずです。

最後に、テンプレートを作っておくといいと感じました。 そもそも他のプロジェクトで使用していた package.json をひっぱって来たのが問題でした。 ですので、それ専用の package.json なりなんなりを作っておけばいい話でした。

ということでテンプレート作りました

今回のプラグインで使用したツール等をまとめ、「npm パッケージ用の TypeScript + Browserify テンプレート」を作成しました。 ご自由にお使いください! ライセンスは CC0 なので、著作権表示等は不要です。

github.com

おわりに

みなさんも npm パッケージ作ってみませんか? 少なくとも自分が不便だから使いたいモジュールを作れば、利用者が 1 人はいるので無駄にはならないです。 今回みたいに、もしデプロイしたパッケージが利用できなくても、個人開発なら怒られないですし。

あと、Git(GitHub)でどういうふうに管理するのがいいのか未だにわからないので、もっと経験を積んだり知識をつけたりしたいです。

おまけ:パッケージとモジュールの違いについて

ファイルをまとめたものがパッケージ、require でロードされるのがモジュールだそうです。

参考↓

better-than-i-was-yesterday.com

GitHub のリポジトリ名が npm-module になっていて、「やっちまったなぁ」と若干後悔しています。