MarkdownをHTMLに変換する

前のセクションではコマンドライン引数で受け取ったファイルを読み込み、標準出力に表示しました。 次は読み込んだMarkdownファイルをHTMLに変換して、その結果を標準出力に表示してみましょう。

markedパッケージを使う

JavaScriptでMarkdownをHTMLへ変換するために、今回はmarkedというライブラリを使用します。 markedのパッケージはnpmで配布されているので、commanderと同様にnpm installコマンドでパッケージをインストールしましょう。

$ npm install --save marked@0.7

インストールが完了したら、Node.jsのスクリプトから読み込みます。 前のセクションの最後で書いたスクリプトに、markedパッケージの読み込み処理を追加しましょう。 次のようにmain.jsを変更し、読み込んだMarkdownファイルをmarkedを使ってHTMLに変換します。 markedパッケージをインポートしたmarked関数は、Markdown文字列を引数にとり、HTML文字列に変換して返します。

main.js

const program = require("commander");
const fs = require("fs");
// markedモジュールをmarkedオブジェクトとしてインポートする
const marked = require("marked");

program.parse(process.argv);
const filePath = program.args[0];

fs.readFile(filePath, { encoding: "utf8" }, (err, file) => {
    if (err) {
        console.error(err.message);
        process.exit(1);
        return;
    }
    // MarkdownファイルをHTML文字列に変換する
    const html = marked(file);
    console.log(html);
});

変換オプションを作成する

markedにはMarkdownの変換オプションがあり、オプションの設定によって変換後のHTMLが変化します。 そこで、アプリケーション中でオプションのデフォルト値を決め、さらにコマンドライン引数から設定を切り替えられるようにしてみましょう。

今回のアプリケーションでは例としてgfmというmarkedのオプションを扱います。

gfmオプション

gfmオプションは、GitHubにおけるMarkdownの仕様(GitHub Flavored Markdown, GFM)に合わせて変換するかを決めるオプションです。 markedではこのgfmオプションがデフォルトでtrueになっています。GFMは標準的なMarkdownにいくつかの拡張を加えたもので、代表的な拡張がURLの自動リンク化です。 次のようにsample.mdを変更し、先ほどのスクリプトとgfmオプションをfalseにしたスクリプトで結果の違いを見てみましょう。

sample.md

# サンプルファイル

これはサンプルです。
https://jsprimer.net/

- サンプル1
- サンプル2

gfmオプションが有効のときは、URLの文字列が自動的に<a>タグのリンクに置き換わります。

<h1 id="サンプルファイル">サンプルファイル</h1>
<p>これはサンプルです。
<a href="https://jsprimer.net/">https://jsprimer.net/</a></p>
<ul>
<li>サンプル1</li>
<li>サンプル2</li>
</ul>

一方、次のようにgfmオプションをfalseにすると、単なる文字列として扱われ、リンクには置き換わりません。

main.js

const program = require("commander");
const fs = require("fs");
const marked = require("marked");

program.parse(process.argv);
const filePath = program.args[0];

fs.readFile(filePath, { encoding: "utf8" }, (err, file) => {
    if (err) {
        console.error(err.message);
        process.exit(1);
        return;
    }
    // gfmオプションを無効にする
    const html = marked(file, {
        gfm: false
    });
    console.log(html);
});
<h1 id="サンプルファイル">サンプルファイル</h1>
<p>これはサンプルです。
https://jsprimer.net/</p>
<ul>
<li>サンプル1</li>
<li>サンプル2</li>
</ul>

自動リンクの他にもいくつかの拡張がありますが、詳しくはGitHub Flavored Markdownのドキュメンテーションを参照してください。

コマンドライン引数からオプションを受け取る

次に、gfmオプションをコマンドライン引数で制御できるようにしましょう。 アプリケーションのデフォルトではgfmオプションを無効にした上で、次のように--gfmオプションを付与してコマンドを実行できるようにします。

$ node main.js --gfm sample.md

コマンドライン引数で--gfmのようなオプションを扱いたいときには、commanderのoptionメソッドを使います。 次のように必要なオプションを定義してからコマンドライン引数をパースすると、program.optsメソッドでパース結果のオブジェクトを取得できます。

// gfmオプションを定義する
program.option("--gfm", "GFMを有効にする");
// コマンドライン引数をパースする
program.parse(process.argv);
// オプションのパース結果をオブジェクトとして取得する
const options = program.opts();
console.log(options.gfm);

--gfmオプションはファイルパスを指定するsample.mdの前と後ろどちらに付いていても動作します。 なぜならprogram.args配列にはprogram.optionメソッドで定義したオプションが含まれないためです。 process.argv配列を直接使っているとこのようなオプションの処理が面倒なので、commanderのようなパース処理を挟むのが一般的です。

デフォルト設定を定義する

アプリケーション側でデフォルト設定を持っておくことで、将来的にmarkedの挙動が変わったときにも影響を受けにくくなります。 次のようにデフォルトのオプションを表現したオブジェクトに対して、program.optsメソッドの戻り値で上書きしましょう。 オブジェクトのデフォルト値を別のオブジェクトで上書きするときには...(spread構文)を使うと便利です。(オブジェクトのspread構文を参照)

// コマンドライン引数のオプションを取得し、デフォルトのオプションを上書きする
const cliOptions = {
    gfm: false,
    ...program.opts(),
};

こうして作成したcliOptionsオブジェクトから、markedにオプションを渡しましょう。 main.jsの全体は次のようになります。

main.js

const program = require("commander");
const fs = require("fs");
const marked = require("marked");

// gfmオプションを定義する
program.option("--gfm", "GFMを有効にする");
program.parse(process.argv);
const filePath = program.args[0];

// コマンドライン引数のオプションを取得し、デフォルトのオプションを上書きする
const cliOptions = {
    gfm: false,
    ...program.opts(),
};

fs.readFile(filePath, { encoding: "utf8" }, (err, file) => {
    if (err) {
        console.error(err.message);
        process.exit(1);
        return;
    }
    const html = marked(file, {
        // オプションの値を使用する
        gfm: cliOptions.gfm,
    });
    console.log(html);
});

定義したコマンドライン引数を使って、Markdownファイルを変換してみましょう。

$ node main.js sample.md
<h1 id="サンプルファイル">サンプルファイル</h1>
<p>これはサンプルです。
https://jsprimer.net/</p>
<ul>
<li>サンプル1</li>
<li>サンプル2</li>
</ul>

また、gfmオプションを付与して実行すると次のように出力されるはずです。

$ node main.js --gfm sample.md 
<h1 id="サンプルファイル">サンプルファイル</h1>
<p>これはサンプルです。
<a href="https://jsprimer.net/">https://jsprimer.net/</a></p>
<ul>
<li>サンプル1</li>
<li>サンプル2</li>
</ul>

これでMarkdown変換の設定をコマンドライン引数でオプションとして与えられるようになりました。 次のセクションではアプリケーションのコードを整理し、最後にユニットテストを導入します。

このセクションのチェックリスト

  • markedパッケージを使ってMarkdown文字列をHTML文字列に変換した
  • コマンドライン引数でmarkedの変換オプションを設定した
  • デフォルトオプションを定義し、コマンドライン引数で上書きできるようにした