Thursday, September 26, 2019 / JavaScript, node.js

自分で書いた JavaScript のモジュールを html と node.js の両方から使える形にビルドする

クライアントサイド/サーバサイド どちらからでも使える Javascript モジュールをつくる場合の備忘録。 ここでは、3x3 マトリックスを使って座標を別の座標に写す処理を行うモジュールを例に node.js のプロジェクトを作成してみます。

座標を マトリックスで変換するモジュール

class MatrixUtils {
    static transform(matrixValues, pt){
        return {
            x: matrixValues[0]*pt.x + matrixValues[1]*pt.y + matrixValues[2]*1,
            y: matrixValues[0+3]*pt.x + matrixValues[1+3]*pt.y + matrixValues[2+3]*1 };
    }
}

これを使う側のコード:

const matrixValues = [
                1,0,10,
                0,1,10,
                0,0, 1];
const pt = {x:0, y:0};
const newPt = MatrixUtils.transform(matrixValues, pt);
console.log( newPt );

node.js のプロジェクトにする

mkdir my-matrix-utils;
cd my-matrix-utils; node init -y
mkdir src;
vi src/myModule.js

myModule.js は、そのままでは index.html や index.js から使うことができないので、以下のように変更:

export class MatrixUtils {
    static transform(matrixValues, pt){
        return {
            x: matrixValues[0]*pt.x + matrixValues[1]*pt.y + matrixValues[2]*1,
            y: matrixValues[0+3]*pt.x + matrixValues[1+3]*pt.y + matrixValues[2+3]*1 };
    }
}

if (typeof window != "undefined") {
    if(!window.MatrixUtils){
        window.MatrixUtils = MatrixUtils;
    }
}

変更点は、以下の2つ。

  • MatrixUtils クラスには export を追加 ( index.js から使うことを想定 )
  • window オブジェクトがあればそこにMatrixUtilsをプロパティとして追加 ( index.html から使うことを想定 )

babel で良い感じに変換して dist/myModule.js を生成

ビルドに必要な babel 関連のツール一式を --save-dev でインストール:

npm install babel-cli babel-plugin-transform-es2015-modules-umd babel-preset-es2015 --save-dev

コマンドラインで作動確認:

./node_modules/.bin/babel --no-babelrc src/myModule.js --out-file dist/myModule.js --presets=es2015 --plugins=transform-es2015-modules-umd

意図通り作動することが確認できたら、 package.json の scripts 部分に以下を追記:

"scripts": {
  "build": "babel --no-babelrc src/myModule.js --out-file dist/myModule.js --presets=es2015 --plugins=transform-es2015-modules-umd"
},

これで npm run build すると babel されて結果が dist/myModule.js に生成されます. (必要なら事前に mkdir dist しておく.)

以上でモジュールの準備ができたので、以下では index.html, index.js などから使う方法を見ていきます。

index.html から使う

まず普通にブラウザ上でこのモジュールを使用します。 mkdir demo; vi demo/index.html して以下の内容にします:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <title>Demo</title>
  <script src="../dist/myModule.js" ></script>
  <script>
    var demo = function(){
        const matrixValues = [
                    1,0,10,
                    0,1,10,
                    0,0, 1];
        const pt = {x:0, y:0};
        const newPt = MatrixUtils.transform(matrixValues, pt);
        console.log( newPt );
    };
  </script>
</head>
<body onload="demo();"></body>
</html>

ブラウザで表示されると console に Object { x: 10, y: 10 } と表示されます。

index.js から使う

今度はサーバサイドでこのモジュールを使用することを想定して node で実行するためのコードを準備します。 vi demo/index.js して以下の内容にします:

var myModule = require('../dist/myModule.js');

const matrixValues = [
            1,0,10,
            0,1,10,
            0,0, 1];

const pt = {x:0, y:0};
const newPt = myModule.MatrixUtils.transform(matrixValues, pt);
console.log( newPt );

node demo/index.jsして作動確認します。

test.js からも使おう

test/test.js に mocha 用のテストをつくり、そこからも モジュールを使用してみます。

npm install mocha --save-dev

してから mkdir test; vi test/test.js を記述します。

const myModule = require('../dist/myModule.js');
const assert = require('assert');

describe('my test', function() {
    it('(0,0)->(10,10)', function() {
        const matrixValues = [1,0,10, 0,1,10, 0,0,1]; 
        const pt = {x:0, y:0};
        const newPt = myModule.MatrixUtils.transform(matrixValues, pt);
        assert.equal(newPt.x, 10);
        assert.equal(newPt.y, 10);
    });
});

package.json の scripts に test を追加:

"scripts": {
    "build": "babel --no-babelrc src/myModule.js --out-file dist/myModule.js --presets=es2015 --plugins=transform-es2015-modules-umd",
    "test": "mocha"
},

npm test してテストを実行します。

transform 処理を Typescript で記述

Javascript のままでは、transform 関数の引数の型が不明瞭なので、Typescript で型を厳密に指定する形でリファクタリングします。

src/myModule.js 内の リファクタリング対象となる transform 関数:

    static transform(matrixValues, pt){
        return {
            x: matrixValues[0]*pt.x + matrixValues[1]*pt.y + matrixValues[2]*1,
            y: matrixValues[0+3]*pt.x + matrixValues[1+3]*pt.y + matrixValues[2+3]*1 };
    }

これを Typescript に書きかえます。諸事情により関数名は myTransform にしておきます:

class Point {
    constructor(public x: number, public y: number){
    }
}

function myTransform(matrixValues: Array<number>, pt: Point): Point{
    return {
        x: matrixValues[0]*pt.x + matrixValues[1]*pt.y + matrixValues[2]*1,
        y: matrixValues[0+3]*pt.x + matrixValues[1+3]*pt.y + matrixValues[2+3]*1 };
}

これを src/myTransform.ts に保存します。

元の src/myModule.js を、この myTransform 関数を使うように修正します。

export class MatrixUtils {
    static transform(matrixValues, pt){
        return myTransform(matrixValues, new Point(pt.x, pt.y));
    }
}

if (typeof window != "undefined") {
    if(!window.MatrixUtils){
        window.MatrixUtils = MatrixUtils;
    }
}

あとは、 myTransform.ts を tsc コマンドで js に変換してから babel するように package.json の build スクリプトを修正します。

"scripts": {
  "build": "tsc src/myTransform.ts --outFile tmp.js;cat tmp.js src/myModule.js | babel --no-babelrc --out-file dist/myModule.js --presets=es2015 --plugins=transform-es2015-modules-umd; rm -f tmp.js",
  "test": "mocha"
},

tsc コマンドを使えるようにするため npm install typescript --save-dev します。

最後に、npm run build して npm test で作動確認して完了です。

まとめ

これでクライントサイドでもサーバサイドでも使えるモジュールが作成できました。