こんにちは。PR TIMESでフロントエンドエンジニアをしている夛田(@unachang113)です。
今回はMarkuplintを導入した話をしようと思います。
Markuplintとは
Markuplintはマークアップ開発者のためのHTMLリンターです。
HTMLのタグが正しいか、Aria属性が適切に設定されているか等の適合性のチェックを行ってくれます。

Markuplint導入の背景
レビュー時に目視でHTML要素の使い方正しいかどうかを確認していくのは、レビュワーのHTML仕様の理解度に左右されてしまいます。
HTMLの品質を担保する場合に個々人のスキルで品質にブレが生じないようにするためにmakruplintを導入することにしました。
Markuplintの導入の進め方について
Markuplintの導入は以下のフェーズで進めていきました。
- Phase1 VS Code上やコマンドを叩いてMarkuplintが実行できるようにする+エラーになったルールを一旦無効化
- Phase2 pre-commit+lint-stagedで実行できるようにする
- Phase3 無効にしたルールを有効化していく + GitHub Actionsで実行できるようにする
Phase1 VS Code上やコマンドを叩いてMarkuplintが実行できるようにする+エラーになったルールを一旦無効化
Markuplintを導入しよう!と決めたとき、実は既にフロントエンドのリポジトリに.markuplintrc が設置されている状態でした。
ただ、2023年頃のコミットを最後に特にメンテナンスもされておらずMarkuplintが動かない状態になっていたため、まずはVS Codeで動くように.markuplintrcの修正を実施するところから始めました。
この時点では以下の修正を行いました。
.markuplintrcの設定を変更markuplint:reccomended-reactをextendsに追加- エラーになったルールを一旦無効にする
- rules,nodeRules合わせて42個のruleを無効にしました
- 各PJのディレクトリに
.markuplintrcを設置 - localでコマンドを叩いて実行できるようにscriptを追加
Phase2 pre-commit+lint-stagedで実行できるようにする
local環境で動くようになったので次はlintでエラーがあった場合、コミットできないようにするためにpre-commit+lint-stagedでコミット時にMakruplintを実行するようにしました。
以下のような形でjsx,tsx,htmlに変更があった場合、Makuplintを実行するように.lintstagedrc.jsonに追記を行っています。
"*.{jsx,tsx,html}": ["markuplint"]Phase3 除外したルールを適用していく + GitHub Actionsで実行できるようにする
Markuplintを実行してコミット時にチェックできる環境が整ったので、Phase1の時に無効にしたルールを有効化していきました。
有効化時に変更が必要だった箇所や対応に苦戦したルールがあったので一部紹介しようと思います。
1. invalid-attr
弊社ではコンポーネントにEmotionを使用している箇所があります。
@emotion/cssでコンポーネントにstyleを渡す場合、 css="XX" という形で渡す必要があります。ただ、cssというattributeはHTMLには存在していないため、ignoreAttrNamePrefixでcssを許可するようにruleを追加して対応を行いました。
"rules": {
"invalid-attr": {
"options": {
// emotionを使っている箇所でcssを設定している箇所があるため無効化
"ignoreAttrNamePrefix": "css"
}
},
}2. required-attr
こちらは物量が多くて修正が大変でした。
主にimg要素のwidth, heightの指定がなく、required-attrのエラーが大量にでていて、愚直にwidth, heightを指定することでエラーを解消していきました。
また、エラーの解消の過程でwidthやheightを100%とパーセント指定で指定していたことにより、別のタイミングでinvalid-attrのルールを適用した際にまたerrorが発生してしまい、二重で大変だった印象があります。
3. attr-duplication
JSX parser環境でy1属性とy2属性を同時に使用した場合に、本来であれば違反しないはずが、attr-duplicationルールに違反しエラーとなる不具合がありました。
こちらは弊社の小張がPRを出してmarkuplint v4.11.5で既に解消されています。
4. heading-levels
見出し要素を使っているコンポーネントが多かったことに加え、エラーが出た箇所を修正した結果、文章のアウトラインがおかしくなってしまうことが多々起こりました。
こちらは実際のページの開発者ツールを開いてアウトラインが正しいかを確認し、エラーが出ているが文章のアウトラインが正しい箇所に関してはruleを無効にして対応しました。
5. pretenders周りの設定
MarkuplintにはpretendersというjsxのファイルをHTMLタグとして隠蔽する機能があります。
実験的な機能ですが自動でpretenders用のjsonファイルを作成する機能が提供されているので、こちらを用いてすべてのカスタムコンポーネントをpretendersで隠蔽して各ルールを適用しようとしましたが、現状はlintでエラーがでた箇所だけPretendersを適用する対応を行っています。
理由としては以下です。
5-1.コンポーネント内でタグのネストがあった場合にエラーになる
コンポーネント内でHTML要素をネストしている場合、pretendersで指定している要素はコンポーネントの一番親の要素になります。例えば、以下のサンプルコードのComponentAようなlabel>inputのコンポーネントの場合、labelでpretendersを指定することになります。
export const App = () => {
const id = uid();
return (
<Component id={id} />
);
}
export const ComponentA = ({ id }) => {
return (
<label><input type="text" id={id} />test</label>
);
};{
"extends": ["markuplint:recommended-react"],
"parser": {
"\\\\.jsx$": "@markuplint/jsx-parser"
},
"specs": {
"\\\\.jsx$": "@markuplint/react-spec"
},
"pretenders": [
{
"selector": "ComponentA",
"as": {
"element": "label",
"inheritAttrs": true
}
}
]
}
上記のサンプルコードで生じたwarningはmarkuplint v4.11.5で修正されていますが、他にも似たようなエラーがpretendersを使用した場合に起きることがあります。(ex. tr,td,dl,dt)
5-2. HTMLの要素名と同じ名前のコンポーネントがある場合にHTML要素も隠蔽されてしまう
pretendersを自動生成した際に以下のようなコードが生成されました。
"pretenders": [
{
"selector": "Label",
"as": {
"element": "span",
"inheritAttrs": true
}
}
]
この場合、Labelという名前のカスタムコンポーネントをspan要素として隠蔽することを想定した記述ですが、HTMLのlabel要素がspan要素として認識されてしまうことがありました。
このケースの場合、コンポーネント名をHTMLの要素名と被らないようにすることでエラーを解消して進めています。
6. markuplintrcの管理方法
.markuplintrc をjsonで記述していたため、無効化したルールに対してコメントを書いていた箇所が軒並みエラーになっている状態でした。この結果、jsonの書き方が間違っていてエラーが起きている場所に気づかないまま開発を行ってしまい、pre-commit時のチェックやCIが正しく回らない事象が度々発生しました。
こちらはjsでMarkuplintのルールを管理するように変更を実施する予定です。
まとめ
上記のフェーズを経て、弊社フロントエンド環境でmakruplintが動くようになりました。
これからもPR TIMESのプロダクトのHTML品質の向上に努めていきます。
We are hiring!
一緒にPR TIMESの開発を担ってくれるエンジニアはもちろん、各種ポジションで採用を行っています!

