MarkdownをHTMLに変換する

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

markedパッケージを使う

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

$ npm install marked@4.0

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

main.js

import { program } from "commander";
import * as fs from "node:fs/promises";
// markedモジュールからmarkedオブジェクトをインポートする
import { marked } from "marked";

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

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

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

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

import { program } from "commander";
import * as fs from "node:fs/promises";
import { marked } from "marked";

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

fs.readFile(filePath, { encoding: "utf8" }).then(file => {
    // gfmオプションを無効にする
    const html = marked.parse(file, {
        gfm: false
    });
    console.log(html);
}).catch(err => {
    console.error(err.message);
    process.exit(1);
});
<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の挙動が変わったときにも影響を受けにくくなります。 次のようにオプションを表現したcliOptionsオブジェクトを作成し、program.optsメソッドの返り値から取得した値をセットします。 コマンドライン引数で指定されなかったオプションには??Nullish coalescing演算子)を使ってデフォルトの値をセットします。 Nullish coalescing演算子は左辺がnullishであるときにだけ右辺の値を返すため、値が指定されなかった状態と明示的にfalseが与えられた状態を区別したいときに便利です。

// コマンドライン引数のオプションを取得する
const options = program.opts();

// コマンドライン引数で指定されなかったオプションにデフォルト値を上書きする
const cliOptions = {
    gfm: options.gfm ?? false,
};

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

main.js

import { program } from "commander";
import * as fs from "node:fs/promises";
import { marked } from "marked";

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

// コマンドライン引数のオプションを取得する
const options = program.opts();

// コマンドライン引数で指定されなかったオプションにデフォルト値を上書きする
const cliOptions = {
    gfm: options.gfm ?? false,
};

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

定義したコマンドライン引数を使って、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の変換オプションを設定した
  • デフォルトオプションを定義し、コマンドライン引数で上書きできるようにした