HTML AST(抽象構文木)とは
HTML AST(Abstract Syntax Tree、抽象構文木)は、HTMLドキュメントを木構造のデータとして表現したものです。Web開発において、HTMLの解析・変換・生成を行う際の基盤技術として広く使われています。
ASTとは
AST(Abstract Syntax Tree) は、ソースコードやマークアップ言語を解析(パース)した結果を、木構造で表現したデータ構造です。
- Abstract(抽象): 構文の本質的な構造だけを抽出
- Syntax(構文): 言語の文法的な構造
- Tree(木): 親子関係を持つ階層的なデータ構造
HTMLがASTに変換される流れ
HTMLテキスト → パーサー → HTML AST → 処理・変換 → HTMLテキスト
具体例
以下のHTMLコード:
<div class="container">
<h1>タイトル</h1>
<p>本文テキスト</p>
</div>
これをASTに変換すると、概念的には以下のような木構造になります:
root
└── element (div)
├── properties: { className: ["container"] }
├── element (h1)
│ └── text: "タイトル"
└── element (p)
└── text: "本文テキスト"
HTML ASTの標準仕様: hast
Web開発でHTML ASTを扱う際のデファクトスタンダードは hast(Hypertext Abstract Syntax Tree) です。
hastの特徴
- unifiedjsエコシステムの一部
- HTMLやSVGを表現するための仕様
- JSON形式で表現可能
- 豊富なプラグインエコシステム
hastのノードタイプ
hastでは、以下のノードタイプが定義されています:
| ノードタイプ | 説明 | 例 |
|---|---|---|
root | ドキュメント全体のルート | - |
element | HTML要素 | <div>, <p>, <a> |
text | テキストコンテンツ | "Hello World" |
comment | HTMLコメント | <!-- コメント --> |
doctype | DOCTYPE宣言 | <!DOCTYPE html> |
hastの構造例
{
"type": "element",
"tagName": "a",
"properties": {
"href": "https://example.com",
"className": ["link", "external"]
},
"children": [
{
"type": "text",
"value": "外部リンク"
}
]
}
unifiedエコシステム
HTML ASTを扱う最も一般的なツールチェーンは、unifiedエコシステムです。
エコシステムの構成
┌─────────────┐
│ unified │ ← 処理のコア
└─────────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ remark │ │ rehype │ │ retext │
│ (Markdown) │ │ (HTML) │ │ (自然言語) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │
mdast hast
(Markdown AST) (HTML AST)
各ツールの役割
| ツール | 対象 | AST形式 |
|---|---|---|
| remark | Markdown | mdast |
| rehype | HTML | hast |
| retext | 自然言語テキスト | nlcst |
処理フロー
Astroなどの静的サイトジェネレーターでは、以下のようなパイプラインでコンテンツを処理します:
Markdown
│
▼ (remark-parse)
mdast (Markdown AST)
│
▼ (remarkプラグイン)
mdast (変換後)
│
▼ (remark-rehype)
hast (HTML AST)
│
▼ (rehypeプラグイン)
hast (変換後)
│
▼ (rehype-stringify)
HTML文字列
HTML ASTの活用例
1. 外部リンクに属性を追加
rehype-external-linksプラグインは、外部リンクにtarget="_blank"を自動追加します。
Before(hast):
{
"type": "element",
"tagName": "a",
"properties": { "href": "https://example.com" },
"children": [{ "type": "text", "value": "リンク" }]
}
After(hast):
{
"type": "element",
"tagName": "a",
"properties": {
"href": "https://example.com",
"target": "_blank",
"rel": ["noopener", "noreferrer"]
},
"children": [{ "type": "text", "value": "リンク" }]
}
2. 見出しにIDを自動付与
rehype-slugプラグインは、見出し要素にIDを自動生成します。
<!-- Before -->
<h2>セクションタイトル</h2>
<!-- After -->
<h2 id="セクションタイトル">セクションタイトル</h2>
3. コードブロックのシンタックスハイライト
rehype-highlightやrehype-prismは、コードブロックにシンタックスハイライトを適用します。
4. 画像の最適化
rehype-img-sizeは画像要素にwidthとheightを自動追加し、レイアウトシフトを防止します。
カスタムrehypeプラグインの作成
HTML ASTを操作するカスタムプラグインを作成できます。
基本構造
import { visit } from 'unist-util-visit';
export function myRehypePlugin() {
return (tree) => {
visit(tree, 'element', (node) => {
// ノードを操作
if (node.tagName === 'a') {
// リンク要素に対する処理
node.properties.className = node.properties.className || [];
node.properties.className.push('custom-link');
}
});
};
}
ユーティリティ関数
hastを操作する際によく使うユーティリティ:
| パッケージ | 用途 |
|---|---|
unist-util-visit | ノードの走査 |
hast-util-select | CSSセレクタでノード検索 |
hast-util-to-string | テキストコンテンツの抽出 |
hast-util-from-parse5 | HTML文字列からhastへ変換 |
hast-util-to-html | hastからHTML文字列へ変換 |
Astroでの活用
AstroではMarkdownやMDXを処理する際に、remarkとrehypeプラグインを設定できます。
// astro.config.mjs
import { defineConfig } from 'astro/config';
import rehypeExternalLinks from 'rehype-external-links';
import rehypeSlug from 'rehype-slug';
export default defineConfig({
markdown: {
rehypePlugins: [
rehypeSlug,
[
rehypeExternalLinks,
{
target: '_blank',
rel: ['noopener', 'noreferrer'],
},
],
],
},
});
詳しくはAstroでのMarkdown処理とプラグインを参照してください。
他の分野でのAST
ASTはHTML以外にも様々な分野で使われています:
| 分野 | ツール例 | AST形式 |
|---|---|---|
| JavaScript | Babel, ESLint | ESTree |
| CSS | PostCSS | PostCSS AST |
| JSON | - | JSON AST |
| GraphQL | graphql-js | GraphQL AST |
まとめ
HTML ASTは、HTMLを構造化されたデータとして扱うための強力な概念です:
- パース: HTMLテキストを木構造に変換
- 変換: プラグインによる自動処理
- 生成: 木構造からHTMLテキストへ
unifiedエコシステム(特にrehype)を活用することで、HTMLの自動変換や最適化を効率的に行えます。静的サイトジェネレーターやビルドツールでは、この仕組みが内部で広く活用されています。