MarkdownをHTMLに変換する
前のセクションではコマンドライン引数で受け取ったファイルを読み込み、標準出力に表示しました。 次は読み込んだMarkdownファイルをHTMLに変換して、その結果を標準出力に表示してみましょう。
marked
パッケージをインストールする
MarkdownをHTMLへ変換するために、今回はmarkedというライブラリを使用します。
markedのパッケージはnpmで配布されているので、npm install
コマンドを使ってインストールできます。
まだ、package.json
を作成していない場合は、先に「Node.jsプロジェクトのセットアップ」を参照してください。
それでは、npm install
コマンドを使ってmarked
パッケージをインストールします。
このコマンドの引数にはインストールするパッケージの名前とそのバージョンを@
記号でつなげて指定できます。
バージョンを指定せずにインストールすれば、その時点での最新の安定版が自動的に選択されます。
次のコマンドを実行して、markedのバージョン14.0をインストールします。1
$ npm install marked@14.0
インストールが完了すると、package.json
ファイルは次のようになっています。
package.json
{
"name": "nodecli",
"version": "1.0.0",
"description": "",
"main": "main.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"marked": "^14.0.0"
}
}
また、npm install
をすると同時にpackage-lock.json
ファイルが生成されています。
このファイルはnpmがインストールしたパッケージの、実際のバージョンを記録するためのものです。
先ほどmarkedのバージョンを14.0
としましたが、実際にインストールされるのは14.0.x
に一致する最新のバージョンです。
package-lock.json
ファイルには実際にインストールされたバージョンが記録されています。
これによって、再びnpm install
を実行したときに、異なるバージョンがインストールされるのを防ぎます。
インストールが完了したら、Node.jsのスクリプトから読み込みます。
前のセクションの最後で書いたスクリプトに、marked
モジュールの読み込み処理を追加しましょう。
次のようにmain.js
を変更し、読み込んだMarkdownファイルをmarkedを使ってHTMLに変換します。
marked
モジュールからインポートしたmarked.parse
関数は、Markdown文字列を引数にとり、HTML文字列に変換して返します。
main.js
import * as util from "node:util";
import * as fs from "node:fs/promises";
// markedモジュールからmarkedオブジェクトをインポートする
import { marked } from "marked";
const {
positionals
} = util.parseArgs({
allowPositionals: true,
});
const filePath = positionals[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>サンプルファイル</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 * as util from "node:util";
import * as fs from "node:fs/promises";
import { marked } from "marked";
const {
positionals
} = util.parseArgs({
allowPositionals: true,
});
const filePath = positionals[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>サンプルファイル</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
のようなフラグを扱いたいときには、parseArg
関数のoptions
オブジェクトに定義します。
options
オブジェクトでは、--key=value
のようなオプションを扱うtype: "string"
と、--flag
のようなフラグを扱うtype: "boolean"
を定義できます。
今回の--gfm
フラグはtype: "boolean"
で定義し、--gfm
フラグがない場合のデフォルト値をfalse
に設定します。
次のようにgfm
フラグを定義してからコマンドライン引数をパースすると、返り値のvalues
でパース結果のオブジェクトを取得できます。
const {
values,
positionals
} = util.parseArgs({
allowPositionals: true,
options: {
// gfmフラグを定義する
gfm: {
// オプションの型をbooleanに指定
type: "boolean",
// --gfmフラグがない場合のデフォルト値をfalseにする
default: false,
}
}
});
// valuesにはオプションのパース結果がオブジェクトとして格納される
console.log(values.gfm); // --gfmフラグがあればtrue、なければfalseとなる
--gfm
フラグは、ファイルパスを指定するsample.md
の前後のどちらについていても動作します。
なぜならpositionals
配列には、options
オブジェクトで定義したオプションのパース結果は含まれないためです。
process.argv
配列を直接使っているとこのようなオプションの処理が面倒なので、parseArg
関数のようなパース処理を挟むのが一般的です。
最後に、main.js
を次のように変更して、--gfm
フラグを使ってgfm
オプションを切り替えられるようにします。
main.js
import * as util from "node:util";
import * as fs from "node:fs/promises";
import { marked } from "marked";
const {
values,
positionals
} = util.parseArgs({
allowPositionals: true,
options: {
gfm: {
type: "boolean",
default: false,
}
}
});
const filePath = positionals[0];
fs.readFile(filePath, { encoding: "utf8" }).then(file => {
const html = marked.parse(file, {
// gfmフラグのパース結果をオプションとして渡す
gfm: values.gfm
});
console.log(html);
}).catch(err => {
console.error(err.message);
process.exit(1);
});
実際にMarkdownファイルを渡して、動作を確認してみましょう。
$ node main.js sample.md
<h1>サンプルファイル</h1>
<p>これはサンプルです。
https://jsprimer.net/</p>
<ul>
<li>サンプル1</li>
<li>サンプル2</li>
</ul>
また、--gfm
フラグを付与して実行すると次のように出力されるはずです。
GFMオプションが有効になっているため、URLがリンクに変換されていることが確認できます。
$ node main.js --gfm sample.md
<h1>サンプルファイル</h1>
<p>これはサンプルです。
<a href="https://jsprimer.net/">https://jsprimer.net/</a></p>
<ul>
<li>サンプル1</li>
<li>サンプル2</li>
</ul>
これでMarkdown変換の設定をコマンドライン引数で与えられるようになりました。 次のセクションではアプリケーションのコードを整理し、最後にユニットテストを導入します。
[コラム] node:
プリフィックス
Node.jsの標準モジュールは、node:util
やnode:fs
のようにnode:
というプリフィックスがモジュール名についています。
このnode:
プリフィックスは後から導入された仕組みであるため、util
やfs
のようにプリフィックスなしでもモジュールを読み込むことができます。
しかしながら、node:
プリフィックスがあることでnpmからインストールしたサードパーティ製のモジュールとの区別が明確になるため、付けておくことが推奨されます。
このセクションのチェックリスト
- markedパッケージを使ってMarkdown文字列をHTML文字列に変換した
- コマンドライン引数でmarkedの変換オプションを設定した
--gfm
フラグを使って、Markdownの変換結果が変わることを確認した
1. --saveオプションをつけてインストールしたのと同じ意味。npm 5.0.0からは--saveがデフォルトオプションとなりました。 ↩