Skip to content
This repository was archived by the owner on Apr 27, 2026. It is now read-only.
This repository was archived by the owner on Apr 27, 2026. It is now read-only.

vmdx 移行計画 (markflow → vmdx) #171

Description

@jp-knj

Implement the following plan:

vmdx 移行計画 (markflow → vmdx)

概要

markflowをベースにvmdxとしてクリーンフォークする。同スコープ(Astro専用、Rust coreベース、WASM含む)。
不要ファイルを除去し、パイプライン設計を改善し、テストをBunに統一、カバレッジ80%以上を目指す。


フェーズ1: リポジトリ初期化 & 不要ファイル除去

1.1 新リポジトリ作成

mkdir vmdx && cd vmdx && git init
# markflowから必要ファイルのみコピー(git history無し、クリーンスタート)

1.2 除去するファイル/ディレクトリ

パス 理由 サイズ
fixtures/integration/withastro-docs/ 大規模外部プロジェクトテスト 4GB+
fixtures/integration/astro-harness/ Astro比較ハーネス 数百MB
docs/ 古いドキュメント。vmdx用に書き直す 1.9MB
scripts/compare-*.mjs markflow固有の比較ツール -
scripts/visual-diff-*.mjs Playwrightビジュアル回帰 -
scripts/ast-compare/ ASTデバッグツール -
scripts/debug/ デバッグユーティリティ -
.github/compatibility.yml markflow互換性マトリクス -

1.3 保持するファイル構成

vmdx/
├── crates/
│   ├── core/           # Rustコアパーサー/レンダラー (7k LOC)
│   │   ├── src/
│   │   ├── tests/      # instaスナップショットテスト
│   │   └── benches/    # Criterionベンチマーク
│   ├── napi/           # N-APIバインディング (1.4k LOC)
│   │   ├── src/
│   │   └── tests/      # → Bunに移行予定
│   └── wasm/           # WASMバインディング (188 LOC)
│       └── src/
├── packages/
│   ├── vmdx/           # 公開JS API (926 LOC)
│   │   └── src/
│   │       ├── node.ts
│   │       ├── browser.ts
│   │       ├── types.ts
│   │       └── registry/
│   ├── astro-vmdx/     # Astro統合 (8k LOC)
│   │   └── src/
│   │       ├── vite-plugin.ts
│   │       ├── pipeline/
│   │       ├── transforms/
│   │       ├── presets/
│   │       └── utils/
│   └── astro-vmdx-loader/  # Content Collectionsローダー (177 LOC)
│       └── src/
├── fixtures/core/      # ユニットテスト用フィクスチャ
├── scripts/
│   └── smoke-napi.mjs  # スモークテスト
├── Cargo.toml
├── package.json
├── pnpm-workspace.yaml
└── README.md           # 新規作成

1.4 検証

  • ls -la で不要ファイルが残っていないことを確認
  • .gitignore を整理

フェーズ2: リネーム (markflow → vmdx)

2.1 Rust側

Cargo.toml (workspace):
members の markflow-* → vmdx-*

crates/core/Cargo.toml:
name = "markflow-core" → "vmdx-core"

crates/napi/Cargo.toml:
name = "markflow-napi" → "vmdx-napi"
dependencies の markflow-core → vmdx-core

crates/wasm/Cargo.toml:
name = "markflow-wasm" → "vmdx-wasm"
dependencies の markflow-core → vmdx-core

2.2 TS側

packages/vmdx/package.json:
name: "markflow" → "vmdx"

packages/astro-vmdx/package.json:
name: "astro-markflow" → "astro-vmdx"
dependencies の markflow → vmdx

packages/astro-vmdx-loader/package.json:
name: "astro-markflow-loader" → "astro-vmdx-loader"

2.3 ソースコード内の参照

  • grep -r "markflow" --include="*.rs" --include="*.ts" --include="*.json" で全参照をリスト
  • エラーメッセージ、ログ、コメント内の「markflow」→「vmdx」
  • N-APIバインディング名: @markflow/napi@vmdx/napi
  • Viteプラグイン名: vite-plugin-markflowvite-plugin-vmdx

2.4 検証

cargo build --workspace
pnpm install
pnpm --dir crates/napi run build:napi
pnpm --dir packages/vmdx run build
pnpm --dir packages/astro-vmdx run build
cargo test --workspace

フェーズ3: テストをBunに統一

3.1 crates/napi テスト移行 (AVA → Bun)

現状のAVAテスト (6ファイル):

  • parse.test.js — 基本パース
  • parseWithOptions.test.js — オプション処理
  • parseWithStats.test.js — 統計情報
  • auto_import.test.js — インポート処理
  • fixtures-parity.test.js — フィクスチャ検証
  • template_literal_bug.test.js — 回帰テスト

移行手順:

  1. bun initcrates/napi/ にBunテスト環境セットアップ
  2. AVAの t.is()expect().toBe() に変換
  3. AVAの t.deepEqual()expect().toEqual() に変換
  4. import test from 'ava'import { test, expect, describe } from 'bun:test'
  5. .test.js.test.ts に変換 (型安全に)
  6. package.jsonava 設定を削除、"test": "bun test" に変更
  7. ava, @ava/typescript を devDependencies から削除

3.2 既存Bunテストの確認

  • packages/markflow/ — 既にBun test
  • packages/astro-markflow/ — 既にBun test
    → これらはリネーム後にそのまま動くことを確認

3.3 検証

bun test (crates/napi/)
pnpm --dir packages/vmdx test
pnpm --dir packages/astro-vmdx test

フェーズ4: パイプライン設計改善

4a. パイプライン再生成の排除

現状の問題:
vite-plugin.ts の3箇所で createPipeline() が呼ばれている:

  • L419: buildStart (バッチ処理)
  • L725: load フック (キャッシュヒット)
  • L849: load フック (キャッシュミス)

対策:

// configResolved or buildStart で1回だけ作成
let pipeline: ReturnType<typeof createPipeline>;

buildStart() {
pipeline = createPipeline({
afterParse: hooks.afterParse,
beforeInject: hooks.beforeInject,
beforeOutput: hooks.beforeOutput,
});
}

// load フックでは pipeline を再利用
load(id) {
const result = await pipeline(ctx);
}

ファイル: packages/astro-vmdx/src/vite-plugin.ts

4b. ExpressiveCode二重処理の統一

現状の問題:

  • blocksToJsx() (blocks-to-jsx.ts L271-282): expressiveCodeComponent オプションでコンポーネント名置換
  • transformExpressiveCode (orchestrator.ts L89): パイプラインでJSX書き換え

対策:

  1. blocksToJsx() から expressiveCodeComponent オプションを削除
  2. blocksToJsx() は常に標準の <pre><code> を出力
  3. transformExpressiveCode パイプラインステップに一元化
  4. テスト更新: blocks-to-jsx.test.ts の EC関連テスト修正

ファイル:

  • packages/astro-vmdx/src/transforms/blocks-to-jsx.ts — EC固有ロジック削除
  • packages/astro-vmdx/src/pipeline/orchestrator.ts — EC処理を完全に担当
  • packages/astro-vmdx/src/transforms/expressive-code.ts — 必要に応じて拡張

4c. Shiki初期化の整理

現状の問題:
Shikiの初期化/条件チェックが散在:

  • L185-198: getShiki() lazy loader定義
  • L410-411: buildStart で初期化
  • L703: load フックで再解決
  • L721, L845: TransformContextに渡す

対策:

// シングルトンパターンに統一
class ShikiManager {
private instance: ShikiHighlighter | null = null;
private config: ShikiConfig;

constructor(config: ShikiConfig) {
this.config = config;
}

async get(): Promise<ShikiHighlighter> {
if (!this.instance) {
this.instance = await createHighlighter(this.config);
}
return this.instance;
}

shouldHighlight(code: string): boolean {
return /<pre[\s>]/.test(code);
}
}

ファイル: packages/astro-vmdx/src/vite-plugin.ts (+ 新規 utils/shiki-manager.ts に切り出し可)

4d. Registry結合の緩和

現状の問題:
blocksToJsx() 内で registry?.getComponent(), registry?.getDirectiveMapping(),
registry?.getSlotNormalization() を直接呼び出し (L251, L277, L320, L328)

対策:

// blocksToJsx の呼び出し側で事前解決
interface ResolvedRegistryData {
components: Map<string, { module: string; exportType: string }>;
directives: Map<string, DirectiveMapping>;
slotNormalizations: Map<string, SlotStrategy>;
}

// vite-plugin.ts で registry → ResolvedRegistryData に変換してから渡す
const resolved = resolveRegistry(registry);
const code = blocksToJsx(blocks, { registry: resolved, ... });

ファイル:

  • packages/astro-vmdx/src/transforms/blocks-to-jsx.ts — Registry直接参照を排除
  • packages/astro-vmdx/src/vite-plugin.ts — 事前解決ロジック追加

4e. Normalize処理の移動

現状の問題:
normalizeSteps / normalizeFileTree がTSパイプラインの最後で実行 (orchestrator.ts L101-106)。
これらは特定コンポーネントの子要素を <ol> で囲むなどの構造変換。

対策 (2つの選択肢):

  1. Rust側に移動: crates/core/src/renderer/mdast/context.rs でSlot描画時に正規化
  • メリット: TS側のパイプラインがシンプルに
  • デメリット: Rustテスト追加が必要
  1. blocksToJsx に統合: blocksToJsx のSlot処理で正規化
  • メリット: 変更範囲が小さい
  • デメリット: blocksToJsx がさらに大きくなる

推奨: 選択肢1 (Rust側に移動)

ファイル:

  • crates/core/src/renderer/mdast/context.rs — Slot正規化ロジック追加
  • packages/astro-vmdx/src/pipeline/orchestrator.ts — normalizeSteps/FileTree 削除
  • packages/astro-vmdx/src/transforms/normalize-*.ts — 削除

フェーズ5: テストカバレッジ80%達成

5.1 現状カバレッジ計測

# Rust
cargo install cargo-tarpaulin
cargo tarpaulin --workspace --out html

# TS
bun test --coverage (各パッケージ)

5.2 テスト追加計画

優先度1: crates/core (Rust)

現状: instaスナップショットテストが主。unit testは限定的。
追加テスト:

  • error.rs (266 LOC): 各エラーバリアントの生成・表示テスト
  • frontmatter.rs (193 LOC): YAML/TOML frontmatter抽出のエッジケース
  • 空frontmatter、不正YAML、ネストされた値
  • slug.rs (2.5k LOC): 日本語/CJK/特殊文字のスラグ生成
  • transform/code_fence.rs (586 LOC): インデント付きコードフェンス、空行含むブロック
  • transform/smartypants.rs (134 LOC): 引用符変換のエッジケース
  • transform/jsx_normalize.rs (936 LOC): ネストされたJSX、self-closing、属性
  • registry/defaults.rs (176 LOC): デフォルトregistryの内容検証

優先度2: packages/astro-vmdx/src/vite-plugin.ts (TS)

現状: 37k LOCだがテストが薄い。plugins.test.ts (8.7k LOC) のみ。
追加テスト:

  • キャッシュヒット/ミスのパス分岐
  • バッチコンパイルのフロー
  • HMR (Hot Module Replacement) の動作
  • エラーハンドリング (コンパイルエラー、frontmatterエラー)
  • 設定バリエーション (Shiki有/無、EC有/無、hooks有/無)

優先度3: crates/wasm (Rust)

現状: テストなし (188 LOC)
追加テスト:

  • 基本パース: compile("# Hello") → 正しいHTML
  • オプション渡し: registryオプション付きコンパイル
  • エラーケース: 不正入力
  • wasm_bindgen のシリアライゼーション

優先度4: packages/vmdx (TS)

現状: validation.test.ts (227 LOC) のみ
追加テスト:

  • node.ts: compile(), compileSync(), batchCompile() の基本動作
  • browser.ts: WASM初期化、コンパイル
  • registry/index.ts: レジストリ作成、プリセットマージ、コンポーネント登録

優先度5: packages/astro-vmdx-loader (TS)

現状: テストなし (177 LOC)
追加テスト:

  • Content Collections loaderの基本動作
  • frontmatter抽出
  • エラーハンドリング

5.3 目標カバレッジ内訳

コンポーネント 目標 備考
crates/core 85% コアロジック、高カバレッジ必要
crates/napi 75% バインディング層、基本パス中心
crates/wasm 70% 小さいので比較的容易
packages/vmdx 80% 公開API、型検証含む
packages/astro-vmdx 80% vite-plugin強化で達成
packages/astro-vmdx-loader 80% 小さいので容易
全体 ≥ 80%

フェーズ6: ビジュアルリグレッションテスト (VRT)

6.1 小規模テストハーネス (5ページ)

markflowのastro-harnessを参考に、vmdx用の軽量ハーネスを構築する。

テストページ構成:

fixtures/vrt-harness/src/content/
├── basic.md          # 見出し、段落、リスト、リンク、画像
├── code-blocks.md    # Shiki/ECコードハイライト、インライン・フェンス
├── directives.md     # aside, note, tip等のディレクティブ
├── components.md     # カスタムコンポーネント、スロット
└── edge-cases.md     # テーブル、脚注、数式、CJK文字

2モードビルド:

# ベースライン (Astroデフォルトmarkdown処理)
VMDX_HARNESS_BASELINE=1 astro build --outDir dist/baseline

# vmdx処理
astro build --outDir dist/vmdx

6.2 セマンティック比較

markflowのcompare-astro-harness.mjsを簡略化して移植:

  • HTML正規化 (scoped class除去、空白正規化、ID正規化)
  • 構造差分 (タグレベル) と コンテンツ差分 (テキストレベル) を分離
  • JSON出力でCI解析可能に

スクリプト: scripts/vrt-semantic.mjs

6.3 ピクセル比較 (Playwright + pixelmatch)

// playwright.config.ts
export default defineConfig({
use: {
viewport: { width: 1280, height: 720 },
// アニメーション無効化CSS注入
},
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
});

比較フロー:

  1. baseline/vmdx両ビルドのHTTPサーバー起動
  2. 各ルートのフルページスクリーンショット取得
  3. pixelmatchで比較 (threshold: 0.1, maxDiffRatio: 0.002)
  4. 差分画像とJSONサマリー生成

スクリプト: scripts/vrt-visual.mjs

6.4 サンプリングテスト (実サイト)

大規模サイトからルートをサンプリングして比較:

  • visual-routes.txt にテスト対象ルートを定義
  • ロケール別に層化サンプリング (EN/JA/ES等)
  • CIではラベルトリガー (visual-diff ラベル) で実行

対象: withastro-docs等の実サイトから40-50ルートを選定

6.5 dev/preview環境の差分検知

dev環境特有の問題検知:

  • HMR後のレンダリング崩れ
  • Vite dev serverでの非同期コンポーネント読み込み
  • CSS Layer順序の違い (dev vs build)

preview環境特有の問題検知:

  • SSG出力のハイドレーション差分
  • ビルド最適化による描画差異
# dev vs preview 比較
astro dev &   # port 4321
astro preview & # port 4322
node scripts/vrt-visual.mjs --baseline=http://localhost:4321 --target=http://localhost:4322

フェーズ7: CI/CD

6.1 GitHub Actions

# .github/workflows/ci.yml
jobs:
rust:
- cargo fmt --check
- cargo clippy --workspace
- cargo test --workspace
- cargo tarpaulin --workspace --fail-under 80

typescript:
- pnpm install
- pnpm --dir crates/napi run build:napi
- pnpm --dir packages/vmdx run build
- pnpm --dir packages/astro-vmdx run build
- bun test (各パッケージ、--coverage)
- typecheck (各パッケージ)

6.2 カバレッジしきい値

  • Rust: cargo tarpaulin --fail-under 80
  • TS: Bun coverageのしきい値設定 (bunfig.toml or スクリプト)

実行順序まとめ

方針: markflowリポジトリ内でリファクタリングを先に行い、安定した状態でフォークする

=== markflow リポジトリ内で実施 ===

ステップ1: パイプライン改善 (既存テストが使える状態で)
4a パイプライン再生成排除 → 既存テストで検証
4b EC二重処理統一 → 既存テストで検証
4c Shiki初期化整理 → 既存テストで検証
4d Registry結合緩和 → 既存テストで検証
4e Normalize処理移動 → 既存テストで検証

ステップ2: テストBun統一
3.1 AVA → Bun移行 (crates/napi)
3.2 全テスト通過確認

=== vmdx リポジトリへフォーク ===

ステップ3: リポジトリ初期化
1.1 新リポジトリ作成
1.2 不要ファイル除去
1.3 ファイル構成確認

ステップ4: リネーム (markflow → vmdx)
2.1 Cargo.toml 更新
2.2 package.json 更新
2.3 ソース内参照置換
2.4 ビルド & テスト通過確認

ステップ5: テストカバレッジ
5.1 現状計測
5.2 優先度順にテスト追加
5.3 80%達成確認

ステップ6: ビジュアルリグレッションテスト (VRT)
7.1 小規模テストハーネス構築 (5ページ)
7.2 Playwright + pixelmatch でdev/preview比較
7.3 サンプリング対象ルート選定

ステップ7: CI/CD
6.1 GitHub Actions設定
6.2 カバレッジしきい値設定
6.3 VRT (ハーネスはCI必須、サンプリングはラベルトリガー)

検証方法

# 全Rustテスト
cargo test --workspace

# Rustカバレッジ
cargo tarpaulin --workspace --fail-under 80

# N-APIビルド + テスト
pnpm --dir crates/napi run build:napi
bun test crates/napi/

# 各TSパッケージ
pnpm --dir packages/vmdx run build && bun test packages/vmdx/
pnpm --dir packages/astro-vmdx run build && bun test packages/astro-vmdx/

# TSカバレッジ
bun test --coverage (各パッケージ)

# スモークテスト
node scripts/smoke-napi.mjs

主要ファイル

ファイル 役割 LOC
crates/core/src/codegen.rs IR生成 1,374
crates/core/src/renderer/mdast/mod.rs MDAST描画 1,230
crates/core/src/transform/jsx_normalize.rs JSX正規化 936
crates/napi/src/lib.rs N-APIエクスポート 23,300
packages/astro-vmdx/src/vite-plugin.ts Viteプラグイン本体 37,000
packages/astro-vmdx/src/pipeline/orchestrator.ts パイプライン ~3,500
packages/astro-vmdx/src/transforms/blocks-to-jsx.ts Block→JSX変換 17,000
packages/astro-vmdx/src/transforms/index.ts Transform登録 3,600

If you need specific details from before exiting plan mode (like exact code snippets, error messages, or content
you generated), read the full transcript at:
/Users/kenji/.claude/projects/-Users-kenji-Projects-astro-markflow/fc0392d4-8d78-40ab-a194-97a994dca955.jsonl

Perf調査(withastro/docs harness / 2026-02-02)

結果サマリ

  • non‑vite が支配的。SSG(ルート生成)が non‑vite の 96% を占める
  • /docs がルート時間の 98% を占める
  • Markflow render 自体は 0.5% 未満(=ボトルネックではない)
  • OG画像生成は 1.6% 程度で軽い

実測(ASTRO_BUILD_PROFILE/ROUTE_PROFILE)

  • total=144,893ms / non‑vite=118,949ms
  • route total=114,490ms(avg 13.63ms)
  • prefix: /docs 112,551ms(98.3%), /open-graph 1,827ms(1.6%)

Markflow render(MARKFLOW_RENDER_PROFILE)

  • total=607ms / 2395 pages(non‑viteの0.5%未満)

Component profiler(ASTRO_COMPONENT_PROFILE)

  • component合計=12,946ms
  • 内訳: Sidebar 10,010ms, PageSidebar 814ms, MarkdownContent 690ms, ToC 314ms

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions