node.jsのモジュールをHTMLで読み込む


背景

HTMLファイルから3rd Partyのnode.jsのモジュールを利用しようとして、「require is not defined」というエラーにぶち当たった。

そこで、node.jsのモジュールをライブラリとしてHTMLに読み込む方法があるかを調べてみたところ、browserifyというものがあるのを知ったので、試してみた。

ちなみに、HTML用のjsファイルが用意されているモジュールもあるっぽいので、その場合は、それをそのまま読み込めばよいと思う。

「require is not defined」エラーについて

例えば、下記のようなhtmlとjsファイルを作ったとき。

test.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Test</title>
  </head>

  <body>
    <p>Test</p>
    <div>
      <textarea id="token" rows="8" cols="120">{hoge: 'hogehoge'}</textarea>
      <textarea id="signed" rows="12" cols="120"></textarea>
    </div>
    <button id="sign">Sign</button>
  </body>

  <script src="test.js"></script>

</html>
test.js
// 注意:このコードは動作しません。

var jwt = require('jsonwebtoken');    

document.getElementById("sign").onclick = () => {
  const jwt = document.getElementById("token").value;
  const signedJwt = sign(jwt);
  document.getElementById("signed").innerText = signedJwt;
};

function sign(payload) {
  return jwt.sign(payload, 'secret');
}

ブラウザで開くと、見た目はこんな感じ。

「Sign」ボタンを押すと、上のテキストエリアにあるJSON形式のテキストに署名をして、下のテキストエリアに表示しようとしたコード。

実際には、このコードは動かなくて、以下のようなエラーになる。

まぁ、当たり前なのかもしれないけど、ただのhtmlなので、require('jsonwebtoken')はエラーになる。

ちなみに、jsonwebtokenはJWTとかに署名できるライブラリで、nodejsであれば「npm install jsonwebtoken」とかでインストール後、上記のような呼び出しで利用できるようになる。

browserifyの利用

ということで、これを動くようにするために、browserifyを使って頑張ってみる。

browserifyは、node.jsのコードをブラウザで動作するようにしてくれるらしい。

最終的にはhtmlとjsだけで動くようにする予定だが、とりあえず、開発にはnode.jsの環境が必要なので環境を作る。

> mkdir browserify
> cd browserify
> yarn init

適当に作ったnode環境のプロジェクト直下に、さきほどのtest.htmlとtest.jsを置く。

続いて、jsonwebtokenをインストール。

> yarn add jsonwebtoken

もちろん、これだけでは状況は変わらない。

ということで、browserifyをインストール。

> yarn global add browserify

browserifyを使って実行してみる。

> browserify test.js -o test2.js

生成された「test2.js」は下記のような感じ。

test2.js
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;...

... (約3万行)

document.getElementById("sign").onclick = () => {
  const jwt = document.getElementById("token").innerText;
  const signedJwt = sign(jwt);
  document.getElementById("signed").innerText = signedJwt;
};

function sign(payload) {
  return jwt.sign(payload, 'secret');
}
},{"jsonwebtoken":208}]},{},[232]);

3万行ぐらいのファイルが生成された。

依存のファイルがすべて一つにまとめられた模様。

test.htmlを以下のように編集して、動作確認。

test.html
<!DOCTYPE html>
<html>
  ...

  <script src="test2.js"></script>

</html>

再度、test.htmlをブラウザで開いてみる。

「Sign」ボタンを押すと、署名されたっぽい文字列が表示された。

とりあえず、それっぽいものが表示されたけど、何か変な気もする。

このままだと、デバッグしようにも、test.jsを書き換えるたびに、browserifyでファイル生成が必要となる。

やはり「jsonwebtoken」部分だけをライブラリとしてhtmlに読み込んで、test.jsから利用するようにしたい。

node.jsのモジュールをライブラリとしてHTMLで読み込む

ということで、「jsonwebtoken」をライブラリとしてHTMLで読み込める形に出来ないか試してみた。

色々試行錯誤してみたところ、下記のようなjsファイル(jsonwebtoken-wrapper.js)を用意し、browserifyをかけることで、それっぽく動作した。

jsonwebtoken-wrapper.js
(function (factory) {
  typeof define === 'function' && define.amd ? define(factory) :
  factory();
}((function () { 'use strict';

  // 以降に、元のライブラリのI/Fをそのままそのまま呼び出すだけのfuntionを追加

  var jwt = require('jsonwebtoken');

  function sign(payload, secretOrPrivateKey, options, callback) {
    return jwt.sign(payload, secretOrPrivateKey, options, callback);
  }

 function verify(jwtString, secretOrPublicKey, options, callback) {
    return jwt.verify(jwtString, secretOrPublicKey, options, callback);
  }

  function decode(jjwt, options) {
    return jwt.decode(jwt, options);
  }

  // 以降で外から呼び出せるように定義する。
  if (window) {
    if (typeof window.define == "function" && window.define.amd) {
      window.define("jwt_sign", function() {
        return sign;
      });
      window.define("jwt_verify", function() {
        return verify;
      });
      window.define("jwt_decode", function() {
        return decode;
      });
    } else {
      window.jwt_sign = sign;
      window.jwt_verify = verify;
      window.jwt_decode = decode;
    }
  }

})));

browserifyを使って、htmlから読み込み可能にしておく。

> browserify jsonwebtoken-wrapper.js -o jsonwebtoken.js

jsonwebtoken.jsというファイルが生成される。

test.htmlは、下記のように修正。

test.html
<!DOCTYPE html>
<html>
  ...
 <body>
    <script src="./jsonwebtoken.js"></script>
    ... 
  </body>

  <script src="test.js"></script>

</html>

ライブラリとして、生成したファイルを読み込むようにした。

また、さきほど、test2.jsに変更した部分をtest.jsに戻しておく。

test.jsは以下のように書き換え。

test.js
// var jwt = require('jsonwebtoken'); この行を削除

...

function sign(payload) {
  return jwt_sign(payload, 'secret'); // jwt.sign(..)をjwt_sign(..)に変更
}

ブラウザにtest.htmlを表示して確認。

とりあえず正しく動作している模様。

ちなみに、「test.html」、「test.js」、「jsonwebtoken.js」の3つだけを適当なフォルダにおいて実行しても、同じように動作した(nodeから切り離された環境でも動作した)。