1 of 24

JavaScriptで

ちゃんとマークアップする

環境を作る

夏のJavascript祭り Online

Webアクセシビリティエンジニア/開発者体験デザイナー: 平尾ゆうてん

https://markuplint.dev ©markuplint under the MIT license.

2 of 24

自己紹介

平尾ゆうてん

  • Webアクセシビリティエンジニア
  • 開発者体験デザイナー
  • 株式会社ディーゼロ所属
  • markuplint 開発者

https://markuplint.dev ©markuplint under the MIT license.

3 of 24

今日話すこと

  • マークアップは大切です
  • マークアップを機械的にチェックする方法
  • markuplintの特徴
  • markuplintの使い方
  • markuplintの設定をいじる
  • markuplintのプラグインの書き方

https://markuplint.dev ©markuplint under the MIT license.

4 of 24

マークアップは大切です

  • マークアップしてますか?
  • 生のHTMLを書くことは少なくなってきてる昨今、でもマークアップはしていると思います。
    • テンプレートエンジン: Pug / EJS / Nunjucks
    • Viewライブラリ: React / Vue / Svalte
  • ちゃんとマークアップしていますか?
  • HTML Living Standardに則ってマークアップしていますか?
  • WAI-ARIAやARIA in HTMLに則ってマークアップしていますか?
  • どんな記法や技術であれ、最終成果物はHTMLです。
  • ちゃんとしたHTMLが作れているのか確認したいですよね。

https://markuplint.dev ©markuplint under the MIT license.

5 of 24

マークアップは大切です

  • なぜマークアップが大切なのか
  • マシンリーダブル(アクセシビリティ・ユニバーサリティ)
    • 適切なレンダーツリーとアクセシビリティツリーをつくる
    • 支援技術(スクリーンリーダー)
    • ロボット(検索エンジン / スクレイピングツール)
    • 機械翻訳
    • リーダー表示
    • スマートスピーカー
    • 新技術での活用・未知の技術への対応

https://markuplint.dev ©markuplint under the MIT license.

6 of 24

マークアップを機械的にチェックする方法

markuplint

eslint-plugin-jsx-a11y

axe

Nu Html Checker

acot

pa11y

miChecker

WAVE

A11yc

エディタ・

アドオン

CLI

(Web API)

(Validator)

ブラウザ・

アドオン

SasS

(Lighthouse)

アプリ

(Koa11y)

Windows専用

エディタ→CLI→API→ブラウザ→SaaS→アプリになるにつれて『コーディング』から遠くなる

https://markuplint.dev ©markuplint under the MIT license.

7 of 24

マークアップを機械的にチェックする方法

markuplint

eslint-plugin-jsx-a11y

axe

Nu Html Checker

acot

pa11y

miChecker

WAVE

A11yc

エディタ・

アドオン

CLI

(Web API)

(Validator)

ブラウザ・

アドオン

SasS

(Lighthouse)

アプリ

(Koa11y)

※Windows

エディタ・CLIまわりのチェック環境を強化したい

https://markuplint.dev ©markuplint under the MIT license.

8 of 24

マークアップを機械的にチェックする方法

markuplintを開発することにしました

https://markuplint.dev ©markuplint under the MIT license.

9 of 24

マークアップを機械的にチェックする方法

https://markuplint.dev ©markuplint under the MIT license.

10 of 24

markuplintの特徴

  • 100% JavaScript
    • Universal JavaScript(Node.js / ブラウザ両対応)
    • npm scripts / VS Code拡張 / ウェブ の3つで動くことを要件として開発
  • 開発環境
    • 開発はTypeScript
    • モノリポジトリでパッケージ管理(lerna + yarnを利用)
    • (まあ、これ自体は珍しくもなんともなく、一般的なパッケージを模倣)
  • 最新バージョン: 1.10.1
  • MITライセンス

https://markuplint.dev ©markuplint under the MIT license.

11 of 24

markuplintの特徴

  • 厳格なHTML構文チェック
    • <ul>直下には<li>, <script>, <template>しかネストできない…など
    • <table>の直下の順番は<thead><tbody><tfoot>…など
    • 属性の内容の型チェック
    • 廃止・非推奨の要素・属性
    • WAI-ARIA/ARIA in HTMLのrole属性・aria属性のチェック
  • 独自要素や属性をルールに追加可能
    • WebComponentsやReact, Vueなどの独自コンポーネントのスキーマを定義できる
    • デザインシステム・コンポーネントカタログとの相性がよい
    • 「<img>は<p>でネストしないとダメ!」みたいなハウスルールも定義できる

https://markuplint.dev ©markuplint under the MIT license.

12 of 24

markuplintの特徴

  • セレクタによるルールの上書き
    • 他のリンターにはない最大の特徴
    • (他のリンターは無効コメント・有効コメントを採用している)
    • 要素起点・class起点・属性起点でルール変更できるので複雑にできる
    • 要素単体もしくは子孫も含めて適応するか設定できる
  • 多くのマークアップシンタックスに対応
    • Pug / PHP / eRuby / EJS / Mustache / Handlebars / Nunjucks / Liquid
    • JSX(React, preact) / Vue / Svelte
    • 追加予定 Astro / HTML Template Literal(lit-html)

https://markuplint.dev ©markuplint under the MIT license.

13 of 24

markuplintの使い方

  • $ npx markuplint --init
    • 自動インストール
    • 設定ファイル .markuplintrc の自動生成
  • $ markuplint *.html *.vue
    • ターゲットファイル(もしくはglob形式)を指定して実行
  • VS Code拡張のインストール
    • 自動で .markuplintrc ファイルを読み込んで、対象のファイルは自動検知

https://markuplint.dev ©markuplint under the MIT license.

14 of 24

markuplintの設定をいじる

  • 設定ファイル
    • .markuplintrc (JSON/YAML形式)
    • markuplintrc.json とか markuplintrc.js とか別名・別形式でもいける
    • cosmiconfigというライブラリに準拠
  • rules / nodeRules / childNodeRules でルールを指定する
    • rulesがグローバルルール(すべての要素に適用)
    • nodeRuleがセレクタで絞り込んだ要素のみに適用(グローバルの設定は上書き)
    • childNodeRuleはセレクタで絞り込んだ要素の子・子孫に適用
    • CSSのカスケーディングっぽい振る舞い

https://markuplint.dev ©markuplint under the MIT license.

15 of 24

markuplintの設定をいじる

「class-naming」ルールを利用してCSS設計の命名規則チェック

(もっと厳密に設定できるように、セレクタを正規表現に対応できるようにしようかな、と考えてたり考えていなかったり…)

<div class="Block">

<div class="Block__Element"></div>

<div class="Block__Element --modifier"></div>

</div>

<!-- Blockにはmodifierを与えない、という運用にしたいとき -->

<div class="Block --modifier">

<div class="Block__Element"></div>

<div class="Block__Element --modifier"></div>

</div>

{

"rules": {

"class-naming": "/^[A-Z][a-z]*$/"

},

"nodeRules": [

{

"selector": "[class*='__']",

"rules": {

"class-naming": [

"/^[A-Z][a-z]*__[A-Z][a-z]*$/",

"/^--[a-z]*$/"

]

}

}

]

}

https://markuplint.dev ©markuplint under the MIT license.

16 of 24

markuplintの設定をいじる

<x-tabs>

<x-tablist>

<x-tab>Tab 1</x-tab>

<x-tab>Tab 2</x-tab>

<x-tab>Tab 3</x-tab>

<x-tab>Tab 4</x-tab>

<x-tab>Tab 5</x-tab>

</x-tablist>

<x-tabpanel>Content 1</x-tabpanel>

<x-tabpanel>Content 2</x-tabpanel>

<x-tabpanel>Content 3</x-tabpanel>

<x-tabpanel>Content 4</x-tabpanel>

<x-tabpanel>Content 5</x-tabpanel>

</x-tabs>

「permitted-contents」ルールを利用して、カスタム要素にルールを設定

"rules": {

"permitted-contents": [

{

"tag": "x-tabs",

"contents": [

{

"require": "x-tablist"

},

{

"oneOrMore": "x-tabpanel"

}

]

},

{

"tag": "x-tablist",

"contents": [

{

"oneOrMore": "x-tab"

}

]

}

]

}

https://markuplint.dev ©markuplint under the MIT license.

17 of 24

markuplintの設定をいじる

「permitted-contents」ルールを利用して、カスタム要素にルールを設定

const Component = () => {

return (

<Tabs>

<TabList>

<Tab>Tab 1</Tab>

<Tab>Tab 2</Tab>

<Tab>Tab 3</Tab>

<Tab>Tab 4</Tab>

<Tab>Tab 5</Tab>

</TabList>

<TabPanel>Content 1</TabPanel>

<TabPanel>Content 2</TabPanel>

<TabPanel>Content 3</TabPanel>

<TabPanel>Content 4</TabPanel>

<TabPanel>Content 5</TabPanel>

</Tabs>

);

};

"rules": {

"permitted-contents": [

{

"tag": "Tabs",

"contents": [

{

"require": "TabList"

},

{

"oneOrMore": "TabPanel"

}

]

},

{

"tag": "TabList",

"contents": [

{

"oneOrMore": "Tab"

}

]

}

]

}

https://markuplint.dev ©markuplint under the MIT license.

18 of 24

markuplintの設定をいじる

  • permitted-contentで設定できること
    • 配列で順番を設定
    • require / optional / oneOrMore / zeroOrMore / min / max で必要個数を設定
    • choiceで「いずれか」を表現
    • interleaveで順不同を表現
    • 内部的にHTMLの要素もこれらで定義している
    • 本家Nu Html Checkerで利用しているスキーマRELAX NGの仕様を拝借

https://markuplint.dev ©markuplint under the MIT license.

19 of 24

markuplintのプラグインの書き方

  • ルール自体もユーザーが独自に開発可能
    • @markuplint/rule-* / markuplint-rule-* というパッケージは自動で読み込み
    • ↑の仕様に気を取られて独自パッケージ読み込み機構がない重大なバグを発見(あとで直す)
    • package.jsonのdependenciesに markuplint-rule-* の名前で file: 参照すればいける

https://markuplint.dev ©markuplint under the MIT license.

20 of 24

markuplintのプラグインの書き方

const { createRule } = require('@markuplint/ml-core');

module.exports = createRule({

defaultLevel: 'warning',

name: 'anyRuleName',

defaultValue: 'defaultValue',

defaultOptions: null,

async verify(document) {

const reports = [];

// ノード(テキストノード含めた要素など)ごとに探索

document.walk(node => {

// .markuplintrcで設定された値もしくはデフォルト値

// nodeから参照するのは、既にセレクタの上書きルール解決済みだから

const value = node.rule.value;

const result = doSomething(node, value);

if (!result) {

reports.push({

severity: node.rule.severity,

message: `${node.name} is bad`,

line: node.startLine,

col: node.startCol,

raw: node.raw,

});

}

});

return reports;

},

});

https://markuplint.dev ©markuplint under the MIT license.

21 of 24

markuplintのプラグインの書き方

  • Document API
    • document.walk で全要素探索(コールバックはAsyncFunction対応)
    • document.walkOn で特定の型の要素のみ探索可能

document.walkOn('Element', element => {});

document.walkOn('ElementCloseTag', closeTag => {});

document.walkOn('Comment', comment => {});

document.walkOn('Text', text => {});

https://markuplint.dev ©markuplint under the MIT license.

22 of 24

markuplintのプラグインの書き方

  • Element API
    • DOM APIに似せたかったけど結構違う(今後の改善課題)

element.attributes; // 属性の参照

element.childNodes; // 子要素の参照

element.classList; // クラスの参照

element.closeTag; // 閉じタグノードの参照

element.isForeignElement; // 外来要素かどうか

element.isCustomElement; // カスタム要素かどうか

element.ownModels; // 自身の所属するコンテントモデル

element.matches('SELECTOR'); // セレクタにマッチするか

element.closest('SELECTOR'); // セレクタにマッチする先祖の参照

https://markuplint.dev ©markuplint under the MIT license.

23 of 24

markuplintのプラグインの書き方

https://markuplint.dev ©markuplint under the MIT license.

24 of 24

JavaScriptで

ちゃんとマークアップする

環境を作る

夏のJavascript祭り Online

Webアクセシビリティエンジニア/開発者体験デザイナー: 平尾ゆうてん

https://markuplint.dev ©markuplint under the MIT license.