久しぶりに、EY-OfficeサイトもJamstackブームに乗ってみようと思う(1) 、EY-OfficeサイトもJamstackブームに乗ってみようと思う(2) の続きです。(2)で問題に成っていた「説明リスト(dl dt dd)が表現されない」を解決できました!
MDN Web Docsより
説明リスト(dl dt dd)が表現されない問題
説明リスト(dl dt dd)は基本的なMarkdownにはありません、よく使われているGitHubのMarkdownでもサポートされていません。しかしJekyllで使われている kramdownにはサポートされているので、EY-Officeサイトでは以下のように使っています。
概要
:
* 既にjQuery等でフロントエンド開発経験のあるソフトウェア技術者がReact(Redux)が理解できる
* Context, Hooks, TypeScriptなどの最新のReactに対応しています
* テスト駆動開発の基本を理解できる
* React 開発プロジェクトにプログラマーとして参加できるようになります
時間
:
* 3日(御社に出向く場合)
内容
:
* JavaScript 復習 (ES6を中心とした補足)
* TypeScript入門
* React入門 (Context, Hooks)
* Redux入門
* フロントエンド開発環境構築
* フロントエンドのテスト駆動開発入門
* React実習
これが以下のようなHTMLに変換されます
<dl>
<dt>概要</dt>
<dd>
<ul>
<li>既にjQuery等でフロントエンド開発経験のあるソフトウェア技術者がReact(Redux)が理解できる</li>
<li>Context, Hooks, TypeScriptなどの最新のReactに対応しています</li>
<li>テスト駆動開発の基本を理解できる</li>
<li>React 開発プロジェクトにプログラマーとして参加できるようになります</li>
</ul>
</dd>
<dt>時間</dt>
<dd>
<ul>
<li>3日(御社に出向く場合)</li>
</ul>
</dd>
<dt>内容</dt>
<dd>
<ul>
<li>3日(御社に出向く場合)</li>
<li>JavaScript 復習 (ES6を中心とした補足)</li>
<li>TypeScript入門</li>
<li>React入門 (Context, Hooks)</li>
<li>Redux入門</li>
<li>フロントエンド開発環境構築</li>
<li>フロントエンドのテスト駆動開発入門</li>
<li>React実習</li>
</ul>
</dd>
<dl>
以前のブログに書いたようにMDXで使っているMarkdown処理系、remark用プラグインremark-deflistをインストールしてみましたが<dd>
の部分には単純な文しか書けない仕様で上手くいきませんでした。
MDXではReactのコンポーネントが使えるので、説明リスト用コンポーネントを作るという方法が使えます。これはReactがわかっていれば作るのは簡単ですが、上のようなmarkdown表記をコンポーネントの表記を置き換える必要がありスマートではありません。
Remarkプラグインを書いた
結局、上のようなmarkdown表記を扱えるremark用プラグインを作る事にしました。remark pluginの作り方にリンクされているCreating a plugin with unifiedなどを読みましたが、なかなか理解できませんでした。検索してもわかりやすい記事は見つけられませんでした。そこでremark-deflistの動きを調査し、改造する方向でプラグインを作り始めました。
注意: まだremark, unist, mdastを深くは理解できてないので、以下の解説、コードには間違いがあるかもしれません。
- remarkプラグインはmarkdownのAST(Abstract Syntax Tree)を辿り(トラバースし)、ASTを書き換えてHTMLを生成します
- 今回作ったプラグインは上のMarkdown表記のみ処理します、汎用性はありません
- このコードで行っている事
unist-util-visit()
を使い、ASTからlist(ul, li)
を取得します、そのlist
の前の要素が文(paragraph)
+:
の場合のみ処理文
+:
+list
から<dl><dt>文</dt>,<dd>list(ul, li)</dd></dl>
のHTMLに変換しますdt, dd
が連続している場合は最初のdl
の子要素に追加する- 不用になった要素は削除、また次に辿る場所を更新する
/**
* Remark Description list plugin.
*
* suported syntax
Term 1
:
* Definition 11
* Definition 12
Term 2
:
* Definition 21
* Definition 22
*/
const visit = require('unist-util-visit')
const isDefList = (node, i, parent) =>
i > 0 &&
node.type === 'list' &&
parent.children[i - 1].type === 'paragraph' &&
/:$/.test(parent.children[i - 1].children[0].value)
const inDefListGroup = (i, parent) =>
i > 1 &&
parent.children[i - 2].type === 'descriptionlist'
const mkTag = (type, tag, children) => ( {
type, data: { hName: tag }, children} )
function descriptionUnorderedList (_options = {}) {
return (tree, _file) => {
visit(tree, ['list'], (node, i, parent) => {
if (!isDefList(node, i, parent)) {
return
}
const term = parent.children[i - 1].children[0]
term.value = term.value.replace(/\n:$/, '')
const descriptionChildIx = inDefListGroup(i, parent) ? i - 2 : -1
const descriptionChild = descriptionChildIx >= 0 ?
parent.children[descriptionChildIx] :
mkTag('descriptionlist', 'dl', [])
descriptionChild.children.push(
mkTag('descriptionterm', 'dt', parent.children[i - 1].children))
descriptionChild.children.push(
mkTag('descriptiondetails', 'dd',
[mkTag('unorderedlist', 'ul', node.children)]))
if (descriptionChildIx >= 0) {
parent.children[descriptionChildIx] = descriptionChild
parent.children.splice(i - 1 , 2)
return [true, i - 2]
} else {
parent.children.splice(i - 1, 2, descriptionChild)
return undefined
}
})
}
}
module.exports = descriptionUnorderedList
これにより、EY-OfficeサイトのJamstack化も一歩進みました 😀