実装例
このページでは、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)
})
})次のステップ
実装例を参考にして、以下のページでさらに詳細な情報を確認してください:
- 型定義 - TypeScript型定義の詳細
- API リファレンス - 関数とクラスの詳細
- トラブルシューティング - よくある問題と解決方法
まとめ
md2formは柔軟性と型安全性を兼ね備えたライブラリです。この実装例を参考に、あなたのプロジェクトに最適なフォーム処理システムを構築してください。