gRPC WebクライアントをESMにビルドする

gRPC WebのクライアントはNode向けのcommonJS形式で出力されますが、commonJSはレガシーなフォーマットになりつつあります。

node-resoleveとcommonjsプラグインを利用してrollupでバンドルすることでESMを出力できます。
package.jsonの記述例は以下のとおりです。

{
    "dependencies": {
        "grpc-web": "^1.2.1"
    },
    "devDependencies": {
        "rollup": "^2.50.3",
        "@rollup/plugin-commonjs": "^19.0.0",
        "@rollup/plugin-node-resolve": "^13.0.0",
        "rollup-plugin-terser": "^7.0.2",
        "google-protobuf": "^3.17.0"
    }
}

gRPC Webが生成するクライアントコードはsome_pb.jssome_grpc_web_pb.jsの2種類です。オプションによりd.tsなども出力できますが、今回の例では、commonjs形式のアウトプットのみ利用します。 rollup.config.jsの記述例は以下のとおりです。

import commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
import {terser} from "rollup-plugin-terser";

const settings = (name) => {
        return {
                input: `./src/${name}.js`,
                output: {
                        file: `dist/${name}_esm.js`,
                        format: 'esm',
                },
                plugins: [
                        resolve(),
                        commonjs(),
                        terser()
                        ]
        }
}

export default [
    settings('some_pb'),
    settings('some_grpc_web_pb')
]

この例ではterser()を利用していますが、なくても問題ありません。出力コードサイズもとくに変わりません。

configが揃ったら、rollupコマンドでビルドできます。
出力されたESMモジュールはアプリケーションからインポートのうえ、規約に沿って利用できます。

ESMのインポート

// export default宣言のモジュールになっているため、命名してインポート
import some_message from './dist/some_pb_esm.js';
import some_service from './dist/some_grpc_web_pb_esm.js';

let client = new some_service.SomePromiseClient('https://example.com:50051', null);
let request = new some_messate.SomeRequest();
request.setParam('some parameter defined by proto');

client.someQuery(request);

export default形式である点を除けば、とくに使い方は変わりません。
ESM形式に変換しておくことで、たとえばdenoからも利用でき、じっさいにdeno bundleの出力コードが動作しました。

ただし、内部的にIIFE形式になっているためTree Shakingが効かず、ミニマムでも500kBほどのパッケージになります。

中馬崇尋
Chuma Takahiro