jdepsから吐き出した依存関係をpngにする with Puppeteer

Node.js

Puppeteerを使ってみたらめっちゃ使いやすくて感動したので勢いでブログを書いています。 サーバーサイドJavaおじさん、齋藤です。

最近はずっとJava漬けでしたが、今日は久しぶりにJavaScriptを書いてみます。

はじめに

Javaにはjdepsというツールがあり、このツールは依存関係を出力してくれます。 JKD8から入っているツールのようです。

以下のブログを参考に使い方を調べていました。

Java 8から付属する、依存関係解析ツールjdepsを試してみる http://d.hatena.ne.jp/torutk/20140624/p1

見てみると、何やら図が出力されています。 これはgraphvizと言われるグラフ可視化ツールのファイルフォーマットを出力できるようです。

では、今回の記事では、node 8とPuppeteerを使って CLIのみ(Headless Chromeが立ち上がりますが)でグラフをpngに出力してみましょう

使うもの

  • Node.js v 8.0.0 (async-await最高)
  • npm 5.0.1
  • Puppeteer (めちゃくちゃインストールが楽)
  • viz.js (graphvizのemscriptenされたもの)
  • jdeps (JDK 1.8.0_131)

jdepsでgraphvizの形式のファイルを出力する

まずはjdepsでgraphvizの形式のファイルと出力します。

-sでgraphviz形式のファイル(summary.dot)を出力してくれます。 -dotoutputでsummary.dotファイルの出力先を変更可能です。 -Rで再帰的に依存関係を調べてくれます。 指定しているのはgradleのjarタスクで出力されたjarファイルです。

jdeps -dotoutput build/reports/deps -s -R build/libs/kinesis-sandbox.jar

classpathを指定すると非常に詳しく調べることが可能です。

jdeps -dotoutput build/reports/deps -s -cp "hoge;fuga;hoga" -R build/libs/kinesis-sandbox.jar

が、しかしgradle使って依存関係を解決しているので 推移的に依存のあるのにクラスパスのリストアップなんてできません。

というわけでgradleのタスクにしてしまいます。 taskにした部分だけ抜粋します。 サマリー形式じゃないものもtask化しました。

jarのファイル名は適宜読み替えてください

// 人間がそれなりに見やすい出力を吐いてくれる
task deps(type: Exec, dependsOn: jar){
    commandLine "jdeps", "-cp", sourceSets.main.runtimeClasspath.asPath, "-R" ,"build/libs/kinesis-sandbox.jar"
}
// graphviz形式のサマリーを指定のフォルダに吐いてくれる
task depsSummary(type: Exec, dependsOn: jar){
    commandLine "jdeps", "-dotoutput", "build/reports/deps", "-s", "-cp", sourceSets.main.runtimeClasspath.asPath, "-R" ,"build/libs/kinesis-sandbox.jar"
}

プロセスを立ち上げてコマンドを叩いているだけですが sourceSets.main.runtimeClasspath.asPathでランタイムのクラスパスを取得できるのでこれを使っています。

作成したタスクは以下の形で叩きます。

./gradlew depsSummary

上記タスクを実行すると ここまででbuild/reports/deps/summary.dotが作成されているはずです。

プロジェクトのセットアップ

まずは準備をしますが、Node、npmは入っているところから始めます。 今回はviz.jsをHeadless Chromeで動かすためにpuppeteerを使います。 また、途中DataURLに付いているBase64をdecodeするためにurlsafe-base64をインストールしています。

npm init -y npm install puppeteer urlsafe-base64 viz.js -S

プロジェクトのセットアップはこれだけです。

PuppeteerとViz.jsを使ってpngを出力してみる

では本題のpng出力をやってみます。 が、短いので先に全部出します。

めっちゃ短い・・・!!!!!!!!!

'use strict'

const fs = require('fs')
const puppeteer = require('puppeteer')
const base64 = require('urlsafe-base64')

fs.readFile('build/reports/deps/summary.dot', (e, data) =>{
    if (e != null ) {
        console.log(e)
        return
    }
    const content = data.toString();
    // Headless Chrome立ち上げ
    puppeteer.launch().then(async browser =>{
        const page = await browser.newPage();
        // viz.jsを読み込んでおく
        await page.injectFile("node_modules/viz.js/viz.js")
        // viz.jsを使ってimgタグのエレメントをbodyにappendしておきます。
        await page.evaluate((content) => {
            const image = Viz(content, {format: 'png-image-element'})
            document.body.appendChild(image)
        }, content);
        // 上のタイミングでsrc属性を取ると取れないので一旦要素がロードされるまで待つ形
        const element = await page.$("img")
        // src にはDataURLが入っている
        const src = await element.evaluate(() => {
            const child=document.body.firstChild
            return child.src;
        })
        // DataURLからpayload部分を取得してdecodeからのpngファイルに書き込み
        const img = base64.decode(src.split(',')[1])
        await new Promise((resolve, reject) =>{
            fs.writeFile("tmp.png", img, function(e){
                if(e === null)resolve()
                else reject(e)
            })
        })
        browser.close()
    })
})

細かい解説

多分コメントで十分だと思うのですが一応、説明が足りない部分を説明していきます。

ドキュメントもそれなりに充実しているので、使い方がわからない場合はそちらを見れば良いと思います。

Headless Chromeの立ち上げは簡単ですね。次のような形で立ち上げが可能です。

puppeteer.launch() // --> Promiseが返却される

pageを開いてviz.jsを読み込んでいます。

const page = await browser.newPage();
// viz.jsを読み込んでおく
await page.injectFile("node_modules/viz.js/viz.js")

また、page.evaluateでブラウザのページのコンテキストで 第一引数に渡したarrow関数を評価しています。 ブラウザのコンテキストなので、関数外のオブジェクトを参照するとエラーになります。 そこで、ここでは第二引数からcontentを渡しています。 ドキュメント的には第二引数以降はSerializableなオブジェクトを渡してくれって書かれています。

await page.evaluate((/* ここの内容は */content) => {
    const image = Viz(content, {format: 'png-image-element'})
    document.body.appendChild(image)
}, /* ここの内容とほぼ同じ */ content);

次のpage.$("img")はどうせbodyの下にimgしか入ってないんだからって感じで雑に書いたSelectorですが jQueryみたいな感じですね。(本当は多分Chromeの開発者ツールで使える$と$$に対応するんでしょうけど) ここで取得したelementという変数はElementHandleというオブジェクトで 実際にブラウザ上で取得できる、DOMではないことに注意してください。

const element = await page.$("img")

Puppeteer部分に関しての説明は大体こんな感じなので 説明はこのぐらいにしておきます。

まとめ

最近少し、フロントエンドから離れてJavaおじさんをやっていた私でも 非常に簡単にgraphvizをwebで動かしてpngの出力をすることができました!

Puppeteerは非常に強力なツールでインストールも使い方も楽でした(インストールしたら勝手にバイナリがダウンロードされます)。 また、Headless Chromeの用途として考えられるテストランナー(mocha-puppeteer)などもすでに出てきており 今後のエコシステムの拡大が楽しみです。

Puppeteerにめっちゃ感動した。