20190323のNode.jsに関する記事は5件です。

Raspberry Piの時刻設定によってMakeがコケた件メモ #webrtcskyway #enebular

次起きた時に焦らないようにメモっておきます。

今回は人感センサでLINE通知する監視カメラを作ろう v2のハンズオンで起きた事象で、(たぶん)会場のネットワークの問題でNTPサーバーに繋げられずRaspberry Piの時刻が前回のハンズオンの日付の2/21になってました。

Raspberry Pi上のNode.jsでjohnny-fivepi-ioのどちらかをインストールする際に内部のmakeがこけました。

npm i johnny-five pi-io

「修正時刻 10659412 は未来の時刻です」みたいな感じのエラーが永遠と出ました。
英語だと make warning: file 'Makefile' has modification time 1.3e+02 s in the future的な感じのエラーみたいです。

参考: 「make: 警告: ファイル Makefile の修正時刻が 1.3e+02 秒分未来の時刻です」について

この記事を見ると時刻設定で問題が出てるってのが分かったので、手動で設定してみます。

参考: 手動で時刻合わせ

$ sudo date --set='2019/03/23 22:51:20'

これでOKです。

一旦モジュールを消してから再度npm iしてみるとうまく行きました。

$ rm -rf node_modules package-lock.json
$ npm i

日付問題があるとmakeがコケるのと会場のネットワークによってこういったことが起きるのは初だったので勉強になりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsのrequireの引数に変数を利用して動的に読み込む

環境

Node.js v10.13.0

本題

requireを使って他のモジュールやファイルを動的に読み込みたい場合、
変数をrequireの引数に利用することになる。

しかし、以下のようにすると上手くいかない。

上手くいかない.js
let path = 'path/foo';
require('./' + path);

require(...)とした場合には引数は静的である必要があるらしい。

なので、以下のようにコールバックを利用して動的な読み込みをおこなう。

正しく動く.js
let path = './path/foo';
require([path], function (foo)
    // path/fooモジュールが変数fooにロードされている
);

コールバックなので若干使い勝手は悪いけれど、
条件によって読み込むモジュールを変更できるのは便利。

参考文献

参考文献というか、公式サイトに上の内容がちゃんと書かれていた...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ローカル開発環境でApollo ClientからMicroへ通信するときのCORS設定

はじめに

node.js によるGraphQLサーバーを実装している時にCORS関係のエラーで嵌ったので共有しておきます。
尚、CORSについては既にいろいろな方が解説されているので説明は省きます。

前提

  • サーバー側: Micro: v9.3.3
  • クライアント側: Apollo Client: v2.4.8
  • サーバー, クライアント共にローカル開発環境で動いている

症状

ローカル開発環境で、GraphQL Playground では動作確認できるのに Apollo Client から正しいGraphQLクエリを投げているのにも関わらず Error: "Network error: NetworkError when attempting to fetch resource." が出る。

原因 & 解決策

CORS の仕様で単純リクエストでないリクエストの時に発生する preflight request にサーバー側が対応できていなかったので対応できるようにする。

preflight request とは

この記事 などで詳しく解説されているので参考にしてください。要は CORS 時に発生することのある OPTION リクエストのことらしいです。 今回の場合は micro-cors によってレスポンスヘッダが適切に設定されているので 200 を返すだけで大丈夫です。

micro-cors を使用しています。

import { send } from 'micro'
import { get, post, router, options } from 'microrouter'
import { ApolloServer } from 'apollo-server-micro'
import { schema } from './schema'

const apolloServer = new ApolloServer({ schema })
const graphqlPath = '/graphql'
const graphqlHandler = apolloServer.createHandler({ path: graphqlPath })

module.exports = cors({
  allowMethods: ['GET', 'POST']
})(
  router(
    post(graphqlPath, graphqlHandler),
    get(graphqlPath, graphqlHandler),
    // to respond with a preflight request
    options(graphqlPath, (_, res) => send(res, 200)),
    (_, res) => send(res, 404, 'Not Found'),
  )
)

若干説明不足な感じもしますが問題解決の一助となれば幸いです。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js でつくる WASM トランスパイラー - 03:ローカル変数を実装する

はじめに

Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニコンパイラー」「ミニインタープリター」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成するトランスパイラーに取り組んでいます。

これまでの取り組み

今回実現したいこと

今回はローカル変数のサポートが目標ですが、合わせて他の機能も実装します。

  • ローカル変数(宣言、参照、代入)
  • 複数行のサポート ... 変数を使って複数の計算を行うため
  • 簡易デバッグ出力 ... 途中の計算結果を確認するため

簡易デバッグ出力

WASMから直接コンソール出力することはできないため、呼び出し元のJavaScript(Node.js)から、コンソール出力用の関数を渡すようにします。

呼びだし元

WASMから使うための関数を用意し、WebAssembly.instantiate()の際に渡します。

呼び出し例(run_wasm_putn.js)
const imports = {
  // WASM側には、imported_putn()という関数が見えている
  imported_putn: function(arg) { 
    console.log(arg);
  }
};

let source = fs.readFileSync(filename);
let typedArray = new Uint8Array(source);

WebAssembly.instantiate(typedArray, 
  { imports: imports }
).then(result => {
  // WASM側では、exported_main() という関数をエキスポートしている
  const ret = result.instance.exports.exported_main();
  process.exit(ret);
}).catch(e => {
  console.log(e);
});

WAST/WASM内

  • 関数をimportして、それを内部の名前に割り付ける
  • 関数呼び出しは call を使う
WASM側での読み込み、関数呼び出し(use_putn.wast)
(module
  ;; --- imported_putn() という外部の関数を、$putn() として内部で呼べるようにする
  (func $putn (import "imports" "imported_putn") (param i32))

  ;; --- $main()関数を、外部にexported_main() という名前で公開する
  (export "exported_main" (func $main))

  ;; --- 実際の処理 ---
  (func $main (result i32)
    ;; --- 外部の関数を呼びだす ---
    (call $putn
      (i32.const 123)
    )
    (i32.const 0)
  )
)

putn()の実行結果

wasm-asで.wastを.wasmに変換し、Node.jsを使って実行します。

$ wasm-as use_putn.wast 
$ node run_wasm_putn.js use_putn.wasm
123

期待通り、123が標準出力に表示されまました。

トランスパイラーの拡張

対象のミニNode.jsコードを用意します。

putn.js
putn(123);

これをパース、単純化すると次の単純化ASTが得られます。

[ 'func_call', 'putn', [ 'lit', 123 ] ]

func_call が関数呼び出しに相当します。今回は簡易版の実装として、putn()だけ呼び出せるような処理を、トランスパイラーのgenerate()関数に追加します。(※lctxについては、あとで説明します)

function generate(tree, indent, lctx) {
  // ... 省略 ...

  if (tree[0] === 'func_call') {  // tree = ['func_call', 'name', arg1, ... ]
    const funcName = tree[1];
    if (funcName === 'putn') {
      return generateCallPutn(tree, indent, lctx);
    }

    println('-- ERROR: unknown func in generate() ---');
    printObj(tree);
    abort();
  }

  // ... 省略 ...
}

// --- debug func putn() ---
function generateCallPutn(tree, indent, lctx) {
  // tree = ['func_call', 'name', arg1, arg2, ... ]

  const valueBlock = generate(tree[2], indent+1, lctx);
  if (! valueBlock) {
    println('---ERROR: empty args for putn() --');
    abort();
  }

  let block = TABs(indent) + '(call $putn' + LF();
  block = block + valueBlock + LF();
  block = block + TABs(indent) + ')';

  return block;
}

関数呼び出しの処理の生成は、新しく用意したgenerateCallPutn()関数で行っています。引数は1個だけを想定していますが、それが式だった場合も想定して再帰的にgenerate()を呼び出しています。

複数行のサポート

せっかくのローカル変数を活かすには、複数行の処理が書きたいところです。例えば次のようなミニNode.jsのコードを用意します。(最後の0(ゼロ)は終了コードになります)

multi_lines.js
putn(1);
putn(123);
0;

これをパース、単純化すると次の単純化ASTが得られます。stmts(statements)が複数行のかたまりに相当します。

[ 'stmts',
  [ 'func_call', 'putn', [ 'lit', 1 ] ],
  [ 'func_call', 'putn', [ 'lit', 123 ] ],
  [ 'lit', 0 ] ]

これに対応するよう、トランスパイラーのgenerate()関数を拡張しておきましょう。

function generate(tree, indent, lctx) {
  // ... 省略 ...

  // --- multi lines ---
  if (tree[0] === 'stmts') {
    let i = 1;
    let block = '';
    while (tree[i]) {
      block = block + generate(tree[i], indent, lctx) + LF() + LF();
      i = i + 1;
    }
    return block;
  }

  // ... 省略 ...
}

ローカル変数のサポート

ローカル変数を扱うために、3つの処理を考えます。

  • 変数の宣言(と初期値の代入): var_decl
  • 変数の再代入: var_assign
  • 変数の参照: var_ref

変数の宣言

ローカル変数の宣言は、初期値がない場合と、初期値がある場合があります。

ミニNode.jsでの表記
let a;
let b = 1;

これをパース、単純化した内部の単純化ASTは次のようになっています。

単純化AST
[ 'var_decl', 'a', null ],
[ 'var_decl', 'b', [ 'lit', 1 ] ],

WASTでの表記

WebAssemblyのテキスト表現WASTでは、ローカル変数に相当する「local $変数名」があるので、それを使います。初期値がある場合は、宣言後に「set_local」を用いて値をセットします。

WASTでの表記
;; let a;
(local $a i32)

;; let b = 1;
(local $b i32)
(set_local $b
  (i32.const 1)
)

トランスパイラーの拡張

まず、宣言されたローカル変数をトランスパイラー内で覚えておくために、ローカルコンテキスト(lctx)というハッシュ(連想配列)を用意します。これが先ほどからgenerate()の引数に追加されていたのです。今回は型もi32の一種類だけなので、シンプルな持ち方にしています。

ローカル変数の保持
lctx['変数名'] = '$変数名' 

これを使って、generate()関数を拡張します。

function generate(tree, indent, lctx) {
  // ... 省略 ...

  // --- local variable --
  if (tree[0] === 'var_decl') {
    const block = declareVariable(tree, indent, lctx);
    return block;
  }

  // ... 省略 ...
}

// --- declare variable ---
function declareVariable(tree, indent, lctx) {
  // -- check NOT exist --
  const name = tree[1];
  if (name in lctx) {
    println('---ERROR: varbable ALREADY exist (compiler) --');
    abort();
  }

  // -- add local variable --
  const varName = '$' + name;
  lctx[name] = varName;

  // --- assign initial value --
  let init = generate(tree[2], indent + 1, lctx);
  let block = '';
  if (init) {
    block = TABs(indent) + '(set_local ' + varName + LF();
    block = block + init + LF();
    block = block + TABs(indent) + ')';
  }

  return block;
}

実際の変数宣言は、declareVariable()関数で生成しています。初期値が式で渡せるように、その評価のためにgenerate()を再帰的に呼び出します。

変数の代入

変数に値を代入する場合、単純化ASTでは var_assign で表現しています。

[ 'var_assign', 'a', 代入する値 ],

代入する値の部分はリテラルだったり、式だったりします。

WASTでは、すでに変数の宣言で登場している「set_local」を使います。generate()関数を拡張し、assignVariable()関数を追加しました。

function generate(tree, indent, lctx) {
  // ... 省略 ...

  // --- local variable --
  if (tree[0] === 'var_assign') {
    const block = assignVariable(tree, indent, lctx);
    return block;
  }

  // ... 省略 ...
}

function assignVariable(tree, indent, lctx) {
  // -- check EXIST --
  const name = tree[1];
  if (name in lctx) {
    let block = '';
    const varName = lctx[name];

    const valueBlock = generate(tree[2], indent + 1, lctx);
    if (!valueBlock) {
      println('---ERROR: var assign value NOT exist --');
      abort();
    }

    block = TABs(indent) + '(set_local ' + varName + LF();
    block = block + valueBlock + LF();
    block = block + TABs(indent) + ')';

    return block;
  }

  println('---ERROR: varibable NOT declarated (assign)--:' + name);
  abort();
}

assignVariable()ではローカルコンテキスト lctx から変数表記を取り出して利用します。代入する内容は例によってgenerate()を呼び出して生成しています。

変数の参照

変数aの値を参照する場合、単純化ASTでは var_ref で表現しています。

[ 'var_ref', 'a' ],

WASTでは、次のように「get_local」を使います。

 (get_local $a)

変数の参照のために generate()関数を拡張し、referVariable()関数を追加しました。

function generate(tree, indent, lctx) {
  // ... 省略 ...

  // --- local variable --
  if (tree[0] === 'var_ref') {
    const block = referVariable(tree, indent, lctx);
    return block;
  }

  // ... 省略 ...
}

// --- variable refer ---
function referVariable(tree, indent, lctx) {
  // -- check EXIST --
  const name = tree[1];
  if (name in lctx) {
    let block = '';
    const varName = lctx[name];

    block = TABs(indent) + '(get_local ' + varName + ')'

    return block;
  }

  println('---ERROR: varibable NOT declarated (ref)--:' + name);
  abort();
}

referVariable()ではローカルコンテキスト lctx から変数表記を取り出して利用します。get_local を使うと、変数の値が読み出されてスタックに積まれます。そのまま式で利用したり、関数の引数として利用することができます。

全体の結合

  • ローカル変数のためのローカルコンテキストの準備
  • 簡易デバッグ出力のための関数 putn() のインポート
  • 出力するWASTのインデント調整

を行うため、処理を追加し、今回のトランスパイラーの完成となります。

function initialLocalContext() {
  const ctx = {
  };
  return ctx;
}
let l_ctx = initialLocalContext(); // top level local context

// --- load and parse source ---
const tree = loadAndParseSrc();

// --- compile to WAST --
const wast = compile(tree, l_ctx);

// ---- compile simplified tree into WAST ---
function compile(tree, lctx) {
  const mainBlock = generate(tree, 2, lctx);
  const varBlock = generateVariableBlock(tree, 2, lctx);

  let block = '(module' + LF();
  // (func $i (import "imports" "imported_func") (param i32))
  block = block + TAB() + '(func $putn (import "imports" "imported_putn") (param i32))' + LF();
  block = block + TAB() + '(export "exported_main" (func $main))' + LF();
  block = block + TAB() + '(func $main (result i32)' + LF();
  block = block + varBlock + LF();
  block = block + mainBlock;
  block = block + TAB() + ')' + LF();
  block = block + ')';

  return block;
}

全体のソースコードは、 mininode_wasm_03.js として GitHub に上げておきます。

変数を使った処理の実行

対象ソース

複数の処理を行う、次のコードを対象にします。

var.js
// --- putn() test ---
putn(1); // 1

// --- declare variable ---
let a = 1 + 2 + 3;
putn(a); // 6

// --- assigne variable, refer variable ---
let b;
b = a + 1;
b = b + 2;
putn(b); // 9
putn(a + b * 2); // 24

b; // expect 9

WASM生成と実行

トランスパイラーのソースコードを、mininode_wasm_03.js とします。

  • トランスパイラーで、sample/var.js → generated.wast
  • wasm-as で、generated.wast → generated.wasm
  • run_wasm_putn.js で実行
$ node mininode_wasm_03.js sample/var.js
$ cat generated.wast
(module
  (func $putn (import "imports" "imported_putn") (param i32))
  (export "exported_main" (func $main))
  (func $main (result i32)
    (local $a i32)
    (local $b i32)

    (call $putn
      (i32.const 1)
    )

    (set_local $a
      (i32.add
        (i32.add
          (i32.const 1)
          (i32.const 2)
        )
        (i32.const 3)
      )
    )

    (call $putn
      (get_local $a)
    )



    (set_local $b
      (i32.add
        (get_local $a)
        (i32.const 1)
      )
    )

    (set_local $b
      (i32.add
        (get_local $b)
        (i32.const 2)
      )
    )

    (call $putn
      (get_local $b)
    )

    (call $putn
      (i32.add
        (get_local $a)
        (i32.mul
          (get_local $b)
          (i32.const 2)
        )
      )
    )

    (get_local $b)

  )
)
$ wasm-as generated.wast
$ node run_wasm_putn.js generated.wasm
Loading wasm file: generated.wasm
1
6
9
24
ret code=9

想定通りの数値が表示され、戻り値も9になりました。

標準出力のテスト

前回までは戻り値を比較するテストを行っていました。今回は標準出力を比較するテストを用意します。比較の対象は、次の3つです。

  • Node.jsで直接実行したときの標準出力
  • ミニNode.jsトランスパイラーで生成したWASMを、Node.jsで実行したときの標準出力
  • ミニNode.jsインタープリターからトランスパイラーを実行して生成されたWASMを、Node.jsで実行したときの標準出力

この3つが一致していたら、テストは成功とします。

Node.jsで直接実行

前処理

実行対象となるソースは次の内容です。

var.js
// --- putn() test ---
putn(1); // 1

// --- declare variable ---
let a = 1 + 2 + 3;
putn(a); // 6

// --- assigne variable, refer variable ---
let b;
b = a + 1;
b = b + 2;
putn(b); // 9
putn(a + b * 2); // 24

b; // expect 9

ここで呼び出している putn() は、今回用意した簡易デバッグ出力関数です。Node.jsで実行するには、この関数を用意する必要があります。
そこで、次のコードを用意しておいて、連結してから実行することにしました。

builtin_helper.js
function putn(n) {
    console.log(n);
}

※ GitHubにあるコードは、以後のステップで使う処理も含んでいるので、違う内容になっています。

これを連結してから実行するように、シェルスクリプトで関数を用意しました。

PreprocessBuiltinForDirect() {
  echo "-- preprocess for builtin func:  src=$jsfile tmp=$direct_file --"
  cat $helper_file > $direct_file # putn(), puts()
  cat $jsfile >>  $direct_file
}
  • $jsfile ... テストに使うjsのファイル名
  • $direct_file ... Node.jsでの直接実行用に一時的に変換したファイル名
  • $helper_file ... 組み込み関数を定義した、ヘルパーファイル名

直接実行

前処理が終わったら、Node.jsで実行します。実行時の標準出力の内容をファイルにリダイレクトして記録しておきます。

NodeDirect() {
  echo "-- node $src --"
  node $direct_file > $direct_stdout
  direct_exit=$?
  echo "direct exit code=$direct_exit"
}

ここで、変数は次を保持しています。

  • $direct_file ... Node.jsでの直接実行用に一時的に変換したファイル名
  • $direct_stdout ... Node.jsで直接実行した際の標準出力を記録しておくファイル名

WASMの生成と実行

js → WASTヘの変換

WASTの生成は 01:初めてのWASMで定数戻り値を返す の処理と同じです。

# -- translate to wast ---
TranslateToWast() {
  echo "--- translate src=$jsfile wast=$wast translater=$translater ---"
  node $translater $jsfile
  if [ "$?" -eq "0" ]
  then
    echo "translate SUCCERSS"
    mv generated.wast $wast_file
  else
    echo "!! translate FAILED !!"
    exit 1
  fi
}

ここで、変数の内容は次の通りです。

  • $translater ... テスト対象になっている、(ミニNode.jsで書かれた)トランスパイラーのファイル名
  • $jsfile ... テストに使うjsのファイル名
  • $wast_file ... 生成するWASTファイル名

WAST → WASM への変換

WAST → WASMへの変換は wasm-as を使います。こちらも 01 同じ処理です。

WastToWasm() {
  echo "--- wast $wast_file to wasm $wasm_file--"
  $wasmas $wast_file
  if [ "$?" -eq "0" ]
  then
    echo "wasm-as SUCCERSS"
  else
    echo "!! wasm-as FAILED !!"
    exit 1
  fi
}

変数の内容は次の通りです。

  • $wasmas ... wasm-as のパス。パスを通しておくか、事前に環境変数WASMAS_FOR_TESTにフルパスを設定しておく
  • $wast_file ... 変換するWASTファイル名
  • $wasm_file ... 変換後のWASMファイル名

WASMの実行

WASMの実行時には、標準出力をファイルにリダイレクトして記録しておきます。

ExecWasm() {
  echo "--- exec $wasm_file from node"
  node $wasm_exec $wasm_file > $wasm_stdout
  wasm_exit=$?
  echo "wasm exit code=$wasm_exit"
}

変数の内容は次の通りです。

  • $wasm_exec ... wasmの実行に使うNode.jsのコード。今回は run_wasm_builtin.js を利用
  • $wasm_file ... 実行するWASMファイル名
  • $wasm_stdout ... 標準出力を記録するファイル名

実行に利用している run_wasm_builtin.js は、この記事の最初に用意した run_wasm_putn.js と同様の処理ですが、 putn() 意外にも今後のステップで利用する他のビルトイン関数も含めて用意しています。 run_wasm_builtin.js については、別の記事で説明する予定です。

ミニインタープリターを使ったWASM生成

引き続き今回のトランスパイラーは、以前つくったミニNode.jsインタープリターで動くことを縛りにしています。
詳細は省略しますが、もう一つWASMファイルを生成、実行しています。実行時の標準出力は、こちらもファイルに記録しておきます。

標準出力のチェック

標準出力の比較では改行文字の違いは無視して diff を取っています。

DiffStdout() {
  diff --strip-trailing-cr $direct_stdout $wasm_stdout > $diff_direct_wasm
  diff --strip-trailing-cr $direct_stdout $interp_wasm_stdout > $diff_direct_interp_wasm
}

ここで変数は次の通りです。

  • $direct_stdout ... Node.jsで直接実行した場合の標準出力の内容
  • $wasm_stdout ... トラインスパイラーで生成した WASM を実行した場合の標準出力の内容
  • $interp_wasm_stdout ... ミニンタープリターから実行したトラインスパイラーで生成した WASM を実行した場合の標準出力の内容
  • $diff_direct_wasm, $diff_direct_interp_wasm ... 標準出力の差分ファイル

その差分の中身が空であればテストは成功、中身が何かあったらテストは失敗とみなします。

CheckStdout() {
  if [ -s $diff_direct_wasm ]
  then
    echo "!!  node <-> wasm stdout are different !!"
    cat $diff_direct_wasm
    exit 1
  else
    echo "... node <-> wasm stdout are same"
  fi

  if [ -s $diff_direct_interp_wasm ]
  then
    echo "!! node <-> inerp-wasm stdout are different !!"
    cat $diff_bin
    exit 1
  else
    echo "... node <-> inerp-wasm stdout are same"
  fi
}

ここまでのテスト実行するシェルスクリプトを、test_stdout.sh とします。

複数テストの実行

前回の 02:四則演算を実装する で用意した複数のテストを拡張して、今回の標準出力の比較を使ったテストを組み込みます。

TestSingleStdout() {
  # --- exec 1 test case --
  testfile=$1

  # usage:
  #  sh test_stdout.sh compilername interpname filename 
  #
  sh test_stdout.sh $compiler $interpreter $testfile
  last_case_exit=$?

  # --- check test result--
  case_count=$(($case_count+1))
  if [ "$last_case_exit" -eq 0 ]
  then
    # -- test OK --
    ok_count=$(($ok_count+1))
    echo "$testfile ... OK" >> $summary_file 
  else
    # -- test NG --
    err_count=$(($err_count+1))
    echo "$testfile ... NG" >> $summary_file 
  fi
}
  • 引数 ... 実行対象のソース
  • $compiler ... トランスパイラー(テストの対象)
  • $interpreter ... 比較に使うミニNode.jsインタープリター

これを使って、これまでのテストをまとめて実行することができます。

# ---- exec test case -----
# step_01
TestSingleExitCode one.js
TestSingleExitCode two.js
TestSingleExitCode eight.js

# step_02
TestSingleExitCode add.js
TestSingleExitCode add_many.js
TestSingleExitCode binoperator.js

# step_03
TestSingleStdout putn.js
TestSingleStdout multi_lines.js
TestSingleStdout var.js

次回は

次回は、比較演算子を実装する予定です。

ここまでのソース

GitHubにソースを上げておきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScript と webpack で作る npm パッケージ

はじめに

TypeScriptwebpack を使って npm パッケージを作成して publish する方法を紹介します。実際に npm パッケージを publish する際に考慮しておいたほうがよい TypeScript や webapck の設定については省略しているので悪しからず。

環境

  • Node.js 10.13.0
  • npm 6.4.1
  • TypeScript: 3.3.4000
  • ts-loader 5.3.3
  • webpack 4.29.6

package.json ファイルの作成

以下のコマンドで package.json ファイルを作成します。

npm init

次の項目の入力を求められますが、すべて後で変更可能なので気軽に決めて OK です。 ただしパッケージ名は重複不可なので、既に同じ名前のものが存在しないか確認しておいた方がよいでしょう。

  • package name
  • version
  • description
  • entry point
  • test command
  • git repository
  • keywords
  • license

パッケージのディレクトリ構成

特に決まりはありませんが、ここでは例として次のような構成で説明します。

.
├── dist // ビルド後の成果物の出力先
├── package.json
└── src // ソースコード

webpack を使ったビルド処理

まず webpackwebpack-cli をインストールします。

npm install --save-dev webpack webpack-cli

次に webpack の設定ファイルを作ります。

モジュールの定義方法によって設定内容は異なりますが、今回は UMD で作ることにします。 library に指定した名前はブラウザ上で実行する際のグローバル変数名となります。ここでは Calc としましたが任意です。なお globalObject については注釈を見てください。1

webpack.config.js
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'dist'),
    library: 'Calc',
    libraryTarget: 'umd',
    globalObject: 'typeof self !== \'undefined\' ? self : this'
  }
};

次に src/index.js を用意します。ここでは例として次のような内容にします。

src/index.js
export function sum(a, b) {
  return a + b;
}

ビルド処理を package.json に記述します。

diff --git a/package.json b/package.json
index 77904e2..9810380 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,8 @@
   "description": "",
   "main": "index.js",
   "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "build": "npx webpack --config webpack.config.js"
   },
   "repository": {
     "type": "git",

試しにビルドしてみましょう。正常にビルドできれば次のような表示がされるはずです。

npm run build

...

> npx webpack --config webpack.config.js

Hash: e08c8cfaa6ee7d9b3aae
Version: webpack 4.29.6
Time: 94ms
Built at: 2019-03-23 08:57:52
   Asset       Size  Chunks             Chunk Names
index.js  956 bytes       0  [emitted]  main
Entrypoint main = index.js
[0] ./src/index.js 28 bytes {0} [built]

ソースコードを TypeScript で書けるようにする

TypeScriptts-loader をインストールします。

npm install --save-dev typescript ts-loader

次に tsconfig.json ファイルを用意します。ここで設定内容を変更しても構いませんが、ひとまずはデフォルト設定のままでも OK です。

npx tsc --init

次に webpack の設定を修正します。

diff --git a/webpack.config.js b/webpack.config.js
index 8cd9058..9d35ec9 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -2,7 +2,15 @@ const path = require('path');

 module.exports = {
   mode: 'development',
-  entry: './src/index.js',
+  entry: './src/index.ts',
+  resolve: {
+    extensions: [".ts", ".js"],
+  },
+  module: {
+    rules: [
+      {test: /\.ts$/, loader: 'ts-loader'},
+    ],
+  },
   output: {
     filename: 'index.js',
     path: path.resolve(__dirname, 'dist')

entry に指定するファイルの拡張子を変更しているので、ファイルもリネームしておきます。

mv src/index.js src/index.ts

これでソースコードを TypeScript で書くことができるようになっているはずです。試しに src/index.ts を次のように書き換えてみます。

src/index.ts
export function sum(a: number, b: number) {
  return a + b;
}

再度ビルドして出力されたファイルを確認すると src/index.ts に書いた内容が出力されていることがわかります。

npm run build
tail -n 10 dist/index.js
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

"use strict";
eval("\nObject.defineProperty(exports, \"__esModule\", { value: true });\nfunction sum(a, b) {\n    return a + b;\n}\nexports.sum = sum;\n\n\n//# sourceURL=webpack://Calc/./src/index.ts?");

/***/ })

/******/ });
});

npm パッケージのエントリーポイントとなるファイルを更新する

ここまでで dist/index.js にビルドされた JavaScript が出力されるようになりました。このファイルが npm パッケージのエントリポイントとなるように package.json を修正します。

diff --git a/package.json b/package.json
index 1b982a9..593a3d5 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "version": "1.0.0",
   "description": "",
-  "main": "index.js",
+  "main": "dist/index.js",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
     "build": "npx webpack --config webpack.config.js",

パッケージの公開前のビルド処理を定義

用意したビルド処理を npm パッケージとして publish する前に行う処理として定義します。 npm-scripts の prepare として定義することで publish する前にビルド処理を行い、生成された成果物をパッケージに含めることができます。

diff --git a/package.json b/package.json
index 1a5ce7e..1b982a9 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
   "main": "index.js",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
-    "build": "npx webpack --config webpack.config.js"
+    "build": "npx webpack --config webpack.config.js",
+    "prepare": "npm run build"
   },
   "repository": {
     "type": "git",

パッケージの公開

まだ npm のアカウントを持っていなければココから作ります。

作成したアカウントでログインします。

npm login

あとは以下のコマンドでパッケージを公開することができます。テストや Linter などを用意している場合は事前に実行するなどして正常に動作することを確認しましょう。

npm publish

バージョンの更新

Semantic Versioning に従って更新するのが一般的です。

  • API に後方互換性のない変更がある場合にはメジャーバーションを更新
  • 後方互換性のある機能追加の場合にはマイナーバーションを更新
  • 後方互換性のあるバグ修正の場合にはパッチバーションを更新

npm のサブコマンドとしてそれぞれ以下のコマンドが用意されているので、変更内容に合うものを使います。

npm version major
npm version minor
npm version patch

実行すると package.json, package-lock.json 内に記述された version プロパティが更新されます。また Git リポジトリであるならばバージョンと同名のタグが作成されます。

パッケージの公開時と同様に、再度 npm publish することでバージョンを更新することができます。

npm publish

  1. 2019-03-23 現在、 UMD で出力する際に起きる問題の回避措置です。将来的には必要なくなっている可能性があります。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む