記事のサマリー(TL;DR)
- HTML 専用 Lint ツール Markuplint は、入れ子の妥当性・必須属性・WAI-ARIA 違反を静的解析で検出し、エラーメッセージを日本語で出力
- JSX(React)・Vue・Svelte・Astro に対応し、
eslint-plugin-jsx-a11yと併用でカバー範囲を拡大できる - サイボウズは kintone のモノレポ(frontend/ 以下に複数パッケージ)に導入し、VS Code 拡張の課題を v5.0.0-alpha.2 の
workingDirectoriesオプションで解消中
kintone・React 系 SaaS 開発チームが HTML Lint を後回しにするリスク
JavaScript には ESLint、CSS には Stylelint が当たり前のように導入されている一方、HTML のチェックが手つかずのプロジェクトは国内でも珍しくありません。ブラウザが多少の誤記を補正して表示するため問題に気づきにくいのが原因ですが、alt の欠落・label の紐付け漏れ・不正な入れ子といった積み重ねは、アクセシビリティ対応(WCAG 準拠)や SEO スコア、コード保守性に長期的な影響を及ぼします。
kintone や Salesforce の専用 UI を Rails などで構築している場合、コンポーネント化が進んでいるほど「カスタムコンポーネントの中の HTML が誰にもチェックされていない」状態になりやすいです。Markuplint の pretenders 機能(コンポーネントと HTML 要素のマッピング)はこの問題を構造的に解決します。また、nodeRules で要素ごとにルールを細粒度に制御できる点は、複数チームが同一リポジトリに共存するモノレポ構成で特に有効です。
詳細
Markuplint とは
Markuplint は HTML 専用の静的解析ツールです。HTML の文法ミス・アクセシビリティ上の問題・非推奨の書き方を検出します。素の HTML だけでなく、JSX(React)・Vue・Svelte・Astro など主要フレームワークのテンプレート内マークアップもチェック対象にできます。
もう一つの特徴は、ルールをチームやプロダクト固有の規約として表現できる柔軟さです。
何をチェックするか
代表的なチェック項目は次のとおりです。
- 要素の入れ子の妥当性(
spanの中にdivは置けない →permitted-contents) - 必須属性の欠落(
imgのalt→required-attr) - アクセシビリティ関連(
labelとの関連付け・tableのcaption・WAI-ARIA の誤用・idの重複) - 非推奨の要素・属性
例として、span の中に div を書いた場合、次のエラーが出力されます。
error: このコンテキストでは、要素spanに要素`div`を含めることはできません (permitted-contents)
エラーメッセージが日本語で出力される点は、日本語話者のチームにとって実用上の利点です。
ESLint プラグインとの違い
React プロジェクトでは eslint-plugin-jsx-a11y が類似のチェックを担います。しかし Markuplint には固有の強みがあります。
| 観点 | Markuplint | eslint-plugin-jsx-a11y |
|---|---|---|
| 要素の親子構造チェック | ✅ permitted-contents |
❌ |
| セレクタによる細粒度制御 | ✅ 強力 | 限定的 |
| JSX 以外の構文サポート | ✅ Vue / Svelte / Astro | ❌ |
| イベントハンドラ起点のチェック | ❌ | ✅ |
両者は置き換えではなく併用が公式でも推奨されており、既存プロジェクトに足す形で導入できます。
導入と設定
--init から始める
npx markuplint --init
対話形式で使用テンプレートエンジンや推奨設定の取り込みを選ぶと、設定ファイル .markuplintrc(または markuplint.config.js)が自動生成されます。
土台となるのは公式の推奨プリセット markuplint:recommended です。extends に指定すると、permitted-contents・required-attr・wai-aria など主要ルールがまとめて有効になります。
Parser と Spec の設定
コンポーネント内のマークアップをチェックするには Parser(構文の解釈)と Spec(フレームワーク固有属性の認識)の2つが必要です。
主要フレームワーク向けのパッケージ対応は次のとおりです。
- Vue:
@markuplint/vue-parser+@markuplint/vue-spec - React:
@markuplint/jsx-parser+@markuplint/react-spec - Svelte:
@markuplint/svelte-parser - Astro:
@markuplint/astro-parser
React の場合の設定例:
{
"extends": ["markuplint:recommended-react"],
"parser": {
"\\.[jt]sx?$": "@markuplint/jsx-parser"
},
"specs": {
"\\.[jt]sx?$": "@markuplint/react-spec"
}
}
@markuplint/react-spec を入れないと、key のような React 固有属性が「HTML 仕様に存在しない属性」として誤検知(invalid-attr)されます。Spec を入れることで誤検知が消え、本当に修正すべき問題だけが残ります。
コンポーネントを HTML 要素として評価する(pretenders)
カスタムコンポーネントの場合、Parser と Spec を入れただけではチェックしきれないケースがあります。たとえば li をコンポーネント化した <ListItem> は、Markuplint からは ul の直下に未知の要素が置かれているように見えるため、ul > li の構造検証ができません。
これを解決するのが pretenders(プリテンダー機能) です。
{
"pretenders": [
{
"selector": "ListItem",
"as": "li"
}
]
}
as はオブジェクト形式でも書け、レンダリング後の属性(attrs)やコンポーネント属性の引き継ぎ(inheritAttrs)まで指定できます。コンポーネントの数だけ設定が増えますが、エラーが出た箇所から少しずつ追加していく運用で十分機能します。
ルールのカスタマイズ
個別ルールのオン・オフ
{
"rules": {
"required-attr": true,
"indentation": false
}
}
false で無効、それ以外の値で有効になります。
nodeRules / childNodeRules による絞り込み
特定の要素にだけルールを効かせたい場合は nodeRules(対象要素そのもの)や childNodeRules(子・子孫)にセレクタで指定します。
{
"rules": {
"required-attr": true
},
"nodeRules": [
{
"selector": "img",
"rules": {
"required-attr": "alt"
}
}
]
}
カスタムルールの自作
チーム固有の規約は createRule でカスタムルールとして実装できます。以下は「.js-toggle クラスを持つ要素に aria-expanded を必須にする」例です。
import { createRule } from '@markuplint/ml-core';
const requireAriaExpanded = createRule({
async verify({ document, report }) {
await document.walkOn('Element', (el) => {
if (
el.classList.contains('js-toggle') &&
!el.hasAttribute('aria-expanded')
) {
report({
scope: el,
message: '.js-toggle 要素には aria-expanded が必要です',
});
}
});
},
});
export default {
name: 'my-rules',
rules: { 'require-aria-expanded': requireAriaExpanded },
};
設定ファイルの plugins に登録し、プラグイン名/ルール名 で有効化します。
{
"plugins": ["./my-plugin.js"],
"rules": {
"my-rules/require-aria-expanded": true
}
}
クラス名と属性のマッピングのようなチーム固有の知識を、組み込みルールと同じ土俵で機械的に担保できるようになります。
既存プロジェクトへの組み込み
CI への組み込み
{
"scripts": {
"lint:markup": "markuplint \"./src/**/*.tsx\""
}
}
package.json の scripts に追加し、CI で実行することで規約外のマークアップがマージされるのを防げます。
VS Code 拡張
Markuplint – Visual Studio Marketplace を入れると、コーディング中にリアルタイムで警告が表示されます。CI で弾かれる前に気づける点で、開発体験の向上に直結します。
kintone モノレポでの活用と課題
サイボウズは kintone のアクセシビリティ確保とコード品質保証を目的として Markuplint を導入しました。kintone は次のような複数パッケージを抱えるモノレポ構成です。
kintone/ # リポジトリのルート
└─ frontend/
├─ pudding/ # チームのディレクトリ
│ ├─ app/
│ └─ mobile-app/
モノレポでのハマりどころ
リポジトリのルートを VS Code で開くと、拡張が次の警告を出して正常に動作しませんでした。
Since markuplint could not be found in the node_modules of the workspace,
this use the version (v4.12.0) installed in VS Code Extension.
各パッケージの下にインストールされている構成では、ルートを開いただけでは拡張がそれを見つけられず、プロジェクト固有の設定やプラグインが反映されないままチェックが走ります。
当面の回避策:ルートではなくパッケージのディレクトリを直接開く。
v5 系で解消される見込み
v5.0.0-alpha.2 で VS Code 拡張に workingDirectories オプションが追加され、モノレポ内の複数パッケージをワーキングディレクトリとして指定できるようになりました。
{
"markuplint.workingDirectories": ["app", "mobile-app"]
}
これを設定することで、ルートを開いたままでも各パッケージの設定と Markuplint が正しく解決されます。v5 系はプレリリース段階のため今後仕様が変わる可能性はありますが、モノレポでエディタ連携を諦めていた場合は試す価値があります。
まとめ
Markuplint は ESLint・Stylelint と並ぶ HTML 向けの静的解析ツールとして、アクセシビリティ違反・入れ子の不正・必須属性の欠落を機械的に検出します。推奨プリセット markuplint:recommended から始め、pretenders や nodeRules、カスタムルールで段階的に精度を上げていくアプローチが現実的です。HTML のチェックが手つかずのプロジェクトでは、まず npx markuplint --init の一手から導入を検討する価値があります。