esbuild で node ファイルを バインド して実行できるようにする

背景

electronでデスクトップアプリを開発してます。
electron-builderでパックするとasarファイルが生成されますが、インストールしたユーザーが見れてしまいセキュリティを考慮しなければなりません。

見られてはいけないコードはasarに入れてはならない。入れるならば難読化が必須です。

今までwebpackを使ってバンドルしてましたが、開発が終わったんで(turboへ移行)代替えを検討しておりました。

vite で renderer, preload, main をバンドルしたら問題が発生。

vite で node ファイルを扱えない問題にぶち当たり、nodeファイルを取り扱う module を asar に入れてしまえば起動はするけど、セキュリティ的に問題がある。node_modules 配下が含まれてしまい見れちゃう。

そこでesbuildを使うことにしました。

esbuld の設定で 下記に設定すれば node ファイルをファイルとして扱ってくれるがロードした後にErrorが発生した。

build({
  ...buildConfig,
  loader: {
    ..buildConfig.loader,
    '.node': 'file', // node ファイルをファイルとして扱う
  },
}

ERROR

TypeError: binding is not a constructor

原因

esbuildでビルドしたコードを確認すると

// node_modules/hoge/fuga.node
var require_fuga = __commonJS({
  "node_modules/hoge/fuga.node"(exports2, module2) {
    module2.exports = "./fuga-WEM7NZ4K.node";
  }
});

となっており、パスが返されていた。

よって、

var binding = require_fuga();

でErrorとなっていた。そりゃエラーになるわw
パスだけ返ってきても困るんだよねぇ。

調査

ってことでバンドルされたjsファイルで直接編集してパスをrequireしてみる

var require_fuga = __commonJS({
	'node_modules/hoge/fuga.node'(exports2, module2) {
		const path = require('path');
		const fs = require('fs');

		// モジュールのパスを解決
		const bindingPath = path.join(__dirname, 'fuga-WEM7NZ4K.node');
		module2.exports = require(bindingPath);
	},
});

結果見事に動いた!後はesbuild でnodeパスをrequireするように書き換えるプラグインを作成すればよいことが判明した。

esbuild カスタムプラグインを作成してみる

const nodeLoaderPlugin = {
	name: 'node-loader',
	setup(build) {
		build.onResolve({ filter: /\.node$/ }, args => {
			return {
				path: path.resolve(args.resolveDir, args.path),
				namespace: 'node-file',
			};
		});

		build.onLoad({ filter: /.*/, namespace: 'node-file' }, args => {
			console.log(`node-file: ${args.path}`);
			const contents = `
		  const path = require('path');
		  const fs = require('fs');
		  const bindingPath = path.join(__dirname, 'fuga-WEM7NZ4K.node');
  
		  module.exports = require(bindingPath); // require して返す!
		});

		// ここで loader の node を file として設定していたが上書かれてバンドルされなくなったのでコピペする
        build.onEnd(result => {
			const srcDir = getSrcDirPath();
			const distDir = getODistDirPath();
			const files = fs.readdirSync(srcDir);
			const nodeFiles = files.filter(file => file.endsWith('.node'));

			nodeFiles.forEach(nodeFile => {
				const srcPath = path.join(srcDir, nodeFile);
				const destFile = `fuga.node`;
				const destPath = path.join(distDir, destFile);
				fs.copyFileSync(srcPath, destPath);
			});
		});


	},
};

以上のプラグインをesbuildに入れて実行してみるとErrorが解消された。

結果

カスタムプラグインを作成してnodeファイルを実行できたが、その後にesbuild作者が解決策をISSUEにて出してくれていたことが発覚!
シンプルで汎用性があり、なんといってもesbuildの作者がいうのであれば間違いないので迷わず採用と合い成った。




この記事が気に入ったらサポートをしてみませんか?