実装例

このページでは、md2formを使った実際の実装例とベストプラクティスを紹介します。基本的な使用方法から、複雑なビジネス要件まで幅広くカバーします。

基本的な使用例

シンプルなお問い合わせフォーム

import { parseMarkdownToForm } from "md2form"
 
const contactFormMd = `
---
collectEmail: true
showProgressBar: false
responseReceipt: always
---
 
# お問い合わせ
 
お気軽にお問い合わせください。
 
## お客様情報
 
### お名前
#type short_text
#placeholder "山田 太郎"
#required true
 
### メールアドレス
#type email
#placeholder "example@example.com"
#required true
 
### 電話番号
#type phone
#placeholder "090-1234-5678"
 
## お問い合わせ内容
 
### 件名
#type short_text
#placeholder "お問い合わせの件名"
#required true
 
### 内容
#type long_text
#placeholder "詳細をお書きください"
#maxLength 1000
#required true
`
 
const contactForm = await parseMarkdownToForm(contactFormMd)
console.log(contactForm)

イベント参加申込フォーム

const eventFormMd = `
---
collectEmail: true
allowMultipleResponses: false
showProgressBar: true
limitResponses: 100
themeColor: "#4F46E5"
---
 
# ITカンファレンス2024 参加申込
 
## 参加者情報
 
### 氏名
#type short_text
#placeholder "フルネームでご記入ください"
#required true
 
### 所属会社
#type short_text
#placeholder "株式会社○○"
#required true
 
### 職種
#type dropdown
#options "エンジニア","デザイナー","プロダクトマネージャー","営業","マーケティング","経営者","その他"
#required true
 
### 経験年数
#type number
#min 0
#max 50
#placeholder "年数を入力"
 
## 参加希望セッション
 
### 興味のあるトラック
#type checkbox
#options "AI・機械学習","Web開発","モバイルアプリ","DevOps","UI/UX","スタートアップ"
#minSelected 1
#maxSelected 3
 
### 懇親会参加
#type boolean
#onLabel "参加する"
#offLabel "参加しない"
#default false
`
 
const eventForm = await parseMarkdownToForm(eventFormMd)

複雑な実装例

顧客満足度調査

const surveyFormMd = `
---
collectEmail: false
allowMultipleResponses: false
showProgressBar: true
shuffleQuestions: true
themeColor: "#059669"
---
 
# 顧客満足度調査
 
ご利用いただき、ありがとうございます。より良いサービス提供のため、ご協力をお願いいたします。
 
## 基本情報
 
### 年代
#type radio
#options "20代以下","30代","40代","50代","60代以上"
 
### 利用頻度
#type dropdown
#options "週1回以上","月2-3回","月1回","年数回","初回利用"
 
## 満足度評価
 
### 総合満足度
#type rating
#scale 5
#labels "不満","とても満足"
#required true
 
### 各項目の評価
#type likert
#statements "商品・サービスの品質","価格の妥当性","スタッフの対応","店舗・施設の環境","利用しやすさ"
#scaleLabels "とても悪い","悪い","普通","良い","とても良い"
#requiredPerStatement true
 
### 改善希望点
#type checkbox
#options "商品の種類を増やす","価格を下げる","営業時間を延長","オンラインサービスの充実","駐車場の拡張","その他"
#maxSelected 3
 
## 追加のご意見
 
### ご意見・ご要望
#type long_text
#placeholder "ご自由にお書きください"
#maxLength 500
 
### 推薦意向
このサービスを友人・知人に推薦したいと思いますか?
 
#type scale
#min 0
#max 10
#minLabel "全く推薦しない"
#maxLabel "非常に推薦する"
`

採用応募フォーム

const recruitmentFormMd = `
---
collectEmail: true
allowMultipleResponses: false
showProgressBar: true
responseReceipt: always
themeColor: "#DC2626"
---
 
# ソフトウェアエンジニア採用応募
 
## 基本情報
 
### 氏名
#type short_text
#placeholder "フルネーム"
#required true
 
### 生年月日
#type date
#required true
 
### 現住所
#type long_text
#placeholder "都道府県市区町村から"
#required true
 
### 電話番号
#type phone
#placeholder "090-1234-5678"
#required true
 
### GitHub URL
#type short_text
#placeholder "https://github.com/username"
 
## 経歴・スキル
 
### 最終学歴
#type dropdown
#options "高等学校","専門学校","高等専門学校","大学","大学院(修士)","大学院(博士)","その他"
 
### プログラミング経験年数
#type number
#min 0
#max 50
#required true
 
### 使用可能な言語・技術
#type checkbox
#options "JavaScript","TypeScript","Python","Java","Go","Rust","React","Vue.js","Angular","Node.js","Docker","Kubernetes","AWS","その他"
 
### 経験レベル自己評価
#type matrix
#rows "フロントエンド開発","バックエンド開発","データベース設計","インフラ・DevOps","プロジェクト管理"
#columns "未経験","基礎レベル","中級レベル","上級レベル","エキスパート"
#cellType radio
#requiredPerRow true
 
## 志望動機・自己PR
 
### 志望動機
#type long_text
#placeholder "当社を志望する理由をお書きください"
#maxLength 1000
#required true
 
### 自己PR
#type long_text
#placeholder "あなたの強みや特技をアピールしてください"
#maxLength 1000
#required true
 
## 応募書類
 
### 履歴書
#type file_upload
#allowedTypes "pdf","docx"
#maxFiles 1
#maxSizeMB 5
#required true
 
### 職務経歴書
#type file_upload
#allowedTypes "pdf","docx"
#maxFiles 1
#maxSizeMB 5
 
### ポートフォリオ
#type file_upload
#allowedTypes "pdf","zip"
#maxFiles 1
#maxSizeMB 10
`

TypeScriptでの型安全な実装

import { parseMarkdownToForm } from "md2form"
import type { FormDocument, ShortText, Rating, Checkbox } from "md2form"
 
// 型安全なフォーム処理
async function processForm(markdown: string) {
  try {
    const form: FormDocument = await parseMarkdownToForm(markdown)
 
    // フォーム情報の取得
    console.log(`フォーム名: ${form.title}`)
    console.log(`ページ数: ${form.pages.length}`)
 
    // 各ページの処理
    form.pages.forEach((page, pageIndex) => {
      console.log(`\n=== ${page.title || `ページ${pageIndex + 1}`} ===`)
 
      page.elements.forEach((element, elementIndex) => {
        // 型に基づいた条件分岐
        switch (element.type) {
          case "short_text":
            const shortText = element as ShortText
            console.log(`テキスト質問: ${shortText.label}`)
            if (shortText.required) {
              console.log("  ※必須項目")
            }
            break
 
          case "rating":
            const rating = element as Rating
            console.log(`評価質問: ${rating.label}`)
            console.log(`  スケール: ${rating.scale}段階`)
            break
 
          case "checkbox":
            const checkbox = element as Checkbox
            console.log(`チェックボックス: ${checkbox.label}`)
            console.log(`  選択肢数: ${checkbox.options.length}`)
            if (checkbox.maxSelected) {
              console.log(`  最大選択数: ${checkbox.maxSelected}`)
            }
            break
 
          default:
            console.log(`その他の質問: ${element.label} (${element.type})`)
        }
      })
    })
 
    return form
  } catch (error) {
    console.error("パースエラー:", error)
    throw error
  }
}

実践的なベストプラクティス

1. フォーム設定の最適化

// 用途別の最適化されたフロントマター設定
 
// アンケート調査用
const surveySettings = `
---
collectEmail: false
allowMultipleResponses: false
showProgressBar: true
shuffleQuestions: true
responseReceipt: never
---`
 
// 顧客登録用
const registrationSettings = `
---
collectEmail: true
allowMultipleResponses: false
showProgressBar: true
responseReceipt: always
themeColor: "#3B82F6"
---`
 
// イベント申込用
const eventSettings = `
---
collectEmail: true
allowMultipleResponses: false
limitResponses: 100
showProgressBar: true
responseReceipt: whenRequested
themeColor: "#10B981"
---`

2. バリデーション戦略

// フォームデータのバリデーション
function validateFormStructure(form: FormDocument) {
  const errors: string[] = []
 
  // 必須要素のチェック
  if (!form.title) {
    errors.push("フォームタイトルが必要です")
  }
 
  if (form.pages.length === 0) {
    errors.push("最低1つのページが必要です")
  }
 
  // ページごとのバリデーション
  form.pages.forEach((page, pageIndex) => {
    if (page.elements.length === 0) {
      errors.push(`ページ${pageIndex + 1}に質問がありません`)
    }
 
    // 必須質問のチェック
    page.elements.forEach((element, elementIndex) => {
      if (element.required && !element.label) {
        errors.push(`ページ${pageIndex + 1}の質問${elementIndex + 1}にラベルがありません`)
      }
    })
  })
 
  return errors
}

3. フォームデータの加工と出力

// JSONファイルへの出力
import { writeFile } from "fs/promises"
import type { FormDocument } from "md2form"
 
async function saveFormToFile(form: FormDocument, filename: string) {
  try {
    const jsonString = JSON.stringify(form, null, 2)
    await writeFile(filename, jsonString, "utf-8")
    console.log(`フォームデータを ${filename} に保存しました`)
  } catch (error) {
    console.error("ファイル保存エラー:", error)
  }
}
 
// 統計情報の取得
import type { FormDocument } from "md2form"
 
function getFormStatistics(form: FormDocument) {
  let totalQuestions = 0
  let requiredQuestions = 0
  const questionTypes: Record<string, number> = {}
 
  form.pages.forEach((page) => {
    page.elements.forEach((element) => {
      totalQuestions++
 
      if (element.required) {
        requiredQuestions++
      }
 
      questionTypes[element.type] = (questionTypes[element.type] || 0) + 1
    })
  })
 
  return {
    pages: form.pages.length,
    totalQuestions,
    requiredQuestions,
    questionTypes,
    settings: form.settings,
  }
}

4. 複数フォームの一括処理

import { readdir, readFile } from "fs/promises"
import path from "path"
 
import { parseMarkdownToForm } from "md2form"
 
// ディレクトリ内のすべてのMarkdownフォームを処理
async function processFormsInDirectory(directoryPath: string) {
  try {
    const files = await readdir(directoryPath)
    const mdFiles = files.filter((file) => path.extname(file) === ".md")
 
    const results = []
 
    for (const file of mdFiles) {
      const filePath = path.join(directoryPath, file)
      const markdown = await readFile(filePath, "utf-8")
 
      try {
        const form = await parseMarkdownToForm(markdown)
        const stats = getFormStatistics(form)
 
        results.push({
          filename: file,
          success: true,
          form,
          stats,
        })
 
        console.log(`✅ ${file}: ${stats.totalQuestions}問の処理完了`)
      } catch (error) {
        results.push({
          filename: file,
          success: false,
          error: error.message,
        })
 
        console.error(`❌ ${file}: ${error.message}`)
      }
    }
 
    return results
  } catch (error) {
    console.error("ディレクトリ処理エラー:", error)
    throw error
  }
}

Webアプリケーションでの使用例

Express.js での実装

import express from "express"
import { parseMarkdownToForm } from "md2form"
 
const app = express()
 
app.post("/api/forms/parse", async (req, res) => {
  try {
    const { markdown } = req.body
 
    if (!markdown) {
      return res.status(400).json({
        error: "Markdownコンテンツが必要です",
      })
    }
 
    const form = await parseMarkdownToForm(markdown)
    const stats = getFormStatistics(form)
 
    res.json({
      success: true,
      form,
      statistics: stats,
    })
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error.message,
    })
  }
})

Next.js での実装

// pages/api/forms/parse.ts
import type { NextApiRequest, NextApiResponse } from "next"
import { parseMarkdownToForm } from "md2form"
 
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== "POST") {
    return res.status(405).json({ error: "Method not allowed" })
  }
 
  try {
    const { markdown } = req.body
    const form = await parseMarkdownToForm(markdown)
 
    res.status(200).json({
      success: true,
      data: form,
    })
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error.message,
    })
  }
}

エラーハンドリングのベストプラクティス

import { parseMarkdownToForm } from "md2form"
 
// 詳細なエラーハンドリング
async function robustFormParsing(markdown: string) {
  try {
    // 基本的なMarkdownバリデーション
    if (!markdown.trim()) {
      throw new Error("Markdownコンテンツが空です")
    }
 
    if (!markdown.includes("#")) {
      throw new Error("有効な見出しが見つかりません")
    }
 
    // パース実行
    const form = await parseMarkdownToForm(markdown)
 
    // 構造バリデーション
    const validationErrors = validateFormStructure(form)
    if (validationErrors.length > 0) {
      console.warn("バリデーション警告:", validationErrors)
    }
 
    return {
      success: true,
      form,
      warnings: validationErrors,
    }
  } catch (error) {
    return {
      success: false,
      error: error.message,
      details: error.stack,
    }
  }
}

パフォーマンス最適化

// 大量のフォーム処理時の最適化
import { Worker } from "worker_threads"
import { parseMarkdownToForm } from "md2form"
 
// ワーカーを使用した並列処理
async function processFormsInParallel(markdownFiles: string[]) {
  const workers = []
  const results = []
 
  const workerCount = Math.min(markdownFiles.length, 4) // CPU数に応じて調整
 
  for (let i = 0; i < workerCount; i++) {
    const worker = new Worker("./form-parser-worker.js")
    workers.push(worker)
  }
 
  // 作業の分散
  const promises = markdownFiles.map((markdown, index) => {
    const worker = workers[index % workerCount]
 
    return new Promise((resolve) => {
      worker.postMessage(markdown)
      worker.once("message", resolve)
    })
  })
 
  const results = await Promise.all(promises)
 
  // ワーカーのクリーンアップ
  workers.forEach((worker) => worker.terminate())
 
  return results
}

テストのベストプラクティス

// Jest を使用したテスト例
import { parseMarkdownToForm } from "md2form"
import type { FormDocument } from "md2form"
 
describe("md2form パーサーテスト", () => {
  test("基本的なフォームパース", async () => {
    const markdown = `
    # テストフォーム
 
    ## セクション1
    ### 質問1
    #type short_text
    #required true
    `
 
    const form = await parseMarkdownToForm(markdown)
 
    expect(form.title).toBe("テストフォーム")
    expect(form.pages).toHaveLength(1)
    expect(form.pages[0].elements).toHaveLength(1)
    expect(form.pages[0].elements[0].type).toBe("short_text")
    expect(form.pages[0].elements[0].required).toBe(true)
  })
 
  test("フロントマター処理", async () => {
    const markdown = `
    ---
    collectEmail: true
    showProgressBar: true
    ---
    # テストフォーム
    ## セクション
    ### 質問
    #type short_text
    `
 
    const form = await parseMarkdownToForm(markdown)
 
    expect(form.settings?.collectEmail).toBe(true)
    expect(form.settings?.showProgressBar).toBe(true)
  })
})

次のステップ

実装例を参考にして、以下のページでさらに詳細な情報を確認してください:

まとめ

md2formは柔軟性と型安全性を兼ね備えたライブラリです。この実装例を参考に、あなたのプロジェクトに最適なフォーム処理システムを構築してください。