概要
emscriptenを利用したライブラリを複数同時に利用しようとすると、メモリー管理用データまわりが競合し、動作しません。
なので、parcelのpackage化するときに、emscriptenで依存するライブラリをすべてコンパイルするというものを書いてみました。
できたもの
これができたプラグインです。
$ npm install --save taktod/parcel-plugin-emc
といった具合に利用します。
こちらはテスト動作用のプロジェクトです。
とりあえず動作させる方法
まず、emscriptenの環境をセットアップします。
emsdk_portableを利用しています。
コンパイル実行コードにパスが通る状況にしてください。
$ emcc --version
これを実行した時にemccのバージョンが表示されればOKです。
次にparcelEmcTestをcloneします。
$ git clone git@github.com:taktod/parcelEmcTest.git
続いてemcLibに移動してnpm installを実行し、必要なライブラリをインストールします。
$ cd parcelEmcTest
$ cd emcLib
$ npm install
emcLib自体は何もする必要はないのですが、parcelのプロジェクトが依存しているモジュールの本体がローカルにある場合は本体側のコードを参照し動作するみたいです。
これによりデバッグは非常に楽でした。
内部のparcel-bunderにアクセスが必要が部分が発生するのでparcel-bundlerをnode_modulesの内部にダウンロードさせるため、npm installを実施しています。
動作自体はc言語の関数が数値を応答するというものです。
この数値の応答をjavascript側で受け取ってconsole.logで出力するということをしています。
最後にparcelTest側のセットアップとテストコードの実行を実施します。
$ cd ../parcelTest
$ npm install
$ npm test
ここまで問題なく実施できたら、適当なブラウザで
http://localhost:1234/
でアクセスするとconsoleに99と表示されると思います。
parcel-pluginの動作
parcel-pluginのポイントは4つの部分みたいです。
- parcel-plugin-xxxという名前
- index.js
- asset
- packager
parcel-plugin-xxxという名称
parcel-plugin-xxxという名前を設定するとparcelコマンド実行時に該当プログラムがプラグインであるとして動作します。
これを入れないと、index.jsに次の設定を入れてもparcelのpluginとして認識してくれません。
なお、自力で実行プログラムに無理やりassetとpackagerを追加して処理することそのものは可能です。
もしやりたい場合はnode_modules/.bin/parcelを色々と書き換えてみれば良いと思います。
index.js
module.exports = (bundler) => {
bundler.addAssetType('emc', require.resolve("./EmcAsset"));
bundler.addPackager('emc', require.resolve("./EmcPackager"));
};
この設定を入れておくと、任意の拡張子を独自のassetと紐付けできます。
また、任意の出力識別子を独自のpackagerと紐付けすることができます。
これらを実行することで特定の拡張子に対して、狙った処理を実施することができました。
なお、設定のない拡張子がきた場合はrawAsset、rawPackagerとして動作するみたいです。
また、既存の拡張子に被せて上書きすることも可能ですが、npm installのタイミング次第でparcel-bundler側が優位になるか、自作のpluginが優位になるかわからないため、独自の定義を書くほうが良いと判断しました。
また、拡張子については、次のようになりました。
.emc.tsみたいな複数のピリオドで区切られたものは.tsが拡張子であると判定されました。
なので、ピリオドを入れた形を利用するのはよくないと思います。
asset
説明としては、とりあえずこのページです。
今回書いたのはこんなプログラムです。
Assetは該当コードが変更された場合やcacheが存在しない場合に処理される模様です。以前cacheがある場合は、plugin側が更新されたとしても、変更されないので、plugin開発時には注意が必要です。
一応--nocacheオプションがあるので、これを使う手もありますが、nocacheにした場合はcacheがそもそも生成されないのでどのような情報が保存されるか確認することができなくなります。
packager
説明はこっち
こちらが今回書いてみたプログラムです。
やることはassetが出力したデータを取りまとめて何らかの処理を実施するのがpackagerの役目
そのまま書き出し処理をすると対応したファイルが出来上がる形になります。
基本的にstartで全体処理前の何かを
addAssetでそれぞれのassetでgenerateした値にアクセス
endで終了処理を実施することができます。
今回やったこと
今回やったことはAssetの処理でemcc(もしくはem++)でc言語ファイルをコンパイルし、objectファイルを生成しておく。
generate関数の応答で他の処理で必要になるものを渡しておく
jsの応答はそのあとでjsPackagerに渡りjavascriptのコードになります。
今回はemscriptenで生成したコードをscriptタグにしてリンクするということをしています。
すでにscriptタグでリンク済みなら処理しないというコードにしてあります。
その後Packagerの処理でassetから渡されてきた、結合時に必要になるldflagsの情報やjavascriptからアクセスする関数名を元にobjectファイルの結合を実施して、最終的なjsファイル、とりあえずemc.js固定を作るようにしています。
結構しっくりとしたコードになったかと思います。
parcel-plugin-emcの使い方
emcという拡張子のファイルを定義します。
内容はjsとして記述する必要があります。
var source = ["src/c/test.c"];
// この_testFuncの方がemscriptenに登録する関数になります。
var func = {testFunc: "_testFunc"};
var cflags = [];
var cppflags = [];
var ldflags = [];
// requireとして渡されるのはfuncの中身になります。
evalでjavascriptとしてコードを実施しています。
source: emscriptenでコンパイルするc言語、もしくはc++言語のコードです。
プログラム的に.cで終わらないファイルについてはem++(c++)としてコンパイルしています。
ソースコードはプロジェクトのrootからの相対パスで記述してください。
func: emscriptenでjavascriptからアクセス可能にする関数を指定します。
jsonの形にしており、keyではなくobject側の値がemscriptenで指定するアクセスしたい関数となります。
先頭に_が入るみたいです。
cflags: c言語のコンパイルを実施してobjectファイルを作るときに渡される追加flagsになります。
cppflags: c++言語のコンパイルを実施してobjectファイルを作るときに渡される追加flagsになります。
ldflags: 生成されたobjectファイルを結合するときに利用する追加flagsになります。
今後したいこと
これをparcelで使えるようにしたいですね。
opus vorbis speex speexdsp soundtouch theoraあたりを使った処理をemscriptenで実施できるようにしておきたい。